Compare commits

..

No commits in common. "master" and "v2.7.1" have entirely different histories.

319 changed files with 10995 additions and 52094 deletions

View file

@ -1,17 +1,3 @@
{
"presets": [["@babel/preset-env", { "loose": true }]],
"env": {
"test": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": true
}
}
]
]
}
}
"presets": ["es2015"]
}

View file

@ -1 +0,0 @@
> 1%

View file

@ -1,3 +0,0 @@
public/**
webpack.config.*.js
*.js

View file

@ -1,4 +0,0 @@
coverage:
parsers:
javascript:
enable_partials: yes

View file

@ -1,3 +0,0 @@
node_modules/
types/
public/

27
.eslintrc Normal file
View file

@ -0,0 +1,27 @@
{
"ecmaFeatures": {
"modules": true
},
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"rules": {
"quotes": [2, "single"],
"strict": [2, "never"],
"indent": ["error", 2, {"SwitchCase": 1}],
"eol-last": "off",
"arrow-body-style": "off",
"no-underscore-dangle": "off",
"no-new": 0,
"max-len": "off",
"no-console": "off",
"consistent-return": "off",
"no-param-reassign": ["error", { "props": false }],
"no-unused-vars": ["error", { "args": "none" }],
"no-lonely-if": "off",
"class-methods-use-this": "error",
"react/require-extension": "off",
},
}

View file

@ -1,123 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier", "sort-class-members"],
"extends": [
"airbnb-base",
"airbnb-typescript",
"plugin:prettier/recommended",
"plugin:compat/recommended",
"plugin:@typescript-eslint/recommended"
],
"env": {
"es6": true,
"browser": true,
"mocha": true,
"cypress/globals": true
},
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 2020
},
"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"
}
],
"lines-between-class-members": "off",
"@typescript-eslint/no-namespace": "off",
"react/jsx-filename-extension": [0]
},
"overrides": [
{
"files": ["*.test.ts"],
"env": {
"mocha": true
},
"rules": {
"no-restricted-syntax": "off",
"compat/compat": "off",
"no-new": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase", "PascalCase", "UPPER_CASE"],
"leadingUnderscore": "allow"
}
]
}
},
{
"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",
"DOMTokenList",
"Object.assign",
"CustomEvent",
"Element.prototype.classList",
"Element.prototype.closest",
"Element.prototype.dataset"
],
"import/resolver": {
"node": {
"extensions": [".js", ".ts"]
}
}
}
}

64
.gitattributes vendored
View file

@ -1,64 +0,0 @@
## GITATTRIBUTES FOR WEB PROJECTS
#
# These settings are for any web project.
#
# Details per file setting:
# text These files should be normalized (i.e. convert CRLF to LF).
# binary These files are binary and should be left untouched.
#
# Note that binary is a macro for -text -diff.
######################################################################
# Auto detect
## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text eol=lf
# Source code
*.css text eol=lf
*.html text diff=html eol=lf
*.js text eol=lf
*.json text eol=lf
*.scss text diff=css eol=lf
*.ts text eol=lf
# Documentation
*.md text eol=lf
*.txt text eol=lf
AUTHORS text eol=lf
CHANGELOG text eol=lf
CHANGES text eol=lf
CONTRIBUTING text eol=lf
COPYING text eol=lf
copyright text eol=lf
*COPYRIGHT* text eol=lf
INSTALL text eol=lf
license text eol=lf
LICENSE text eol=lf
NEWS text eol=lf
readme text eol=lf
*README* text eol=lf
TODO text eol=lf
# Linters
.eslintrc text eol=lf
.stylelintrc text eol=lf
# Configs
.babelrc text eol=lf
.browserslistrc text eol=lf
.editorconfig text eol=lf
.env text eol=lf
.gitattributes text eol=lf
.gitconfig text eol=lf
package-lock.json text -diff eol=lf
*.npmignore text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
browserslist text eol=lf
# Graphics
# SVG treated as an asset (binary) by default.
*.svg text eol=lf
*.png binary

View file

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,28 +0,0 @@
## Description
<!--- 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. -->
## 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)
- [ ] Refactor (non-breaking change which maintains existing functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
## Checklist
<!--- 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.
- [ ] I have added new tests for the bug I fixed/the new feature I added.
- [ ] I have modified existing tests for the bug I fixed/the new feature I added.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

View file

@ -1,14 +0,0 @@
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);

View file

@ -1,92 +0,0 @@
const { readFileSync, writeFileSync, mkdirSync } = require('fs');
const path = require('path');
const { once } = require('events');
const puppeteer = require('puppeteer');
const pixelmatch = require('pixelmatch');
const { PNG } = require('pngjs');
const server = require('../../server');
async function test() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const artifactsPath = 'screenshot';
const snapshotName = `puppeteer-${process.platform}.png`;
let error;
let pixelDifference;
let diff;
if (!server.listening) await once(server, 'listening');
try {
page.on('console', msg => {
if (msg.type() === 'error') throw new Error(msg.text());
});
page.on('pageerror', err => {
throw err;
});
await page.goto(`http://127.0.0.1:${server.address().port}`, {
waitUntil: 'networkidle2',
});
await page.setViewport({ width: 640, height: 1000 });
await page.waitForTimeout(500); // Wait for resize to complete
await page.click('label[for="choices-single-custom-templates"]');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
mkdirSync(artifactsPath, { recursive: true });
const imageBuffer = await page.screenshot({
path: path.join(artifactsPath, snapshotName),
fullPage: true,
});
// compare with snapshot
const screenshot = PNG.sync.read(imageBuffer);
const snapshot = PNG.sync.read(
readFileSync(path.resolve(__dirname, `./__snapshots__/${snapshotName}`)),
);
const { width, height } = screenshot;
diff = new PNG({ width, height });
pixelDifference = pixelmatch(
screenshot.data,
snapshot.data,
diff.data,
width,
height,
{
threshold: 0.6,
},
);
} catch (err) {
console.error(err);
error = err;
} finally {
if (diff) {
writeFileSync(path.join(artifactsPath, 'diff-' + snapshotName), PNG.sync.write(diff));
}
await Promise.all([
browser.close(),
new Promise(resolve => server.close(resolve)),
]);
}
if (pixelDifference > 200) {
console.error(
`Snapshot is different from screenshot by ${pixelDifference} pixels`,
);
process.exit(1);
}
if (error) process.exit(1);
}
process.on('unhandledRejection', err => {
console.error(err);
process.exit(1);
});
process.once('uncaughtException', err => {
console.error(err);
process.exit(1);
});
setImmediate(test);

View file

@ -1,155 +0,0 @@
const path = require('path');
const { readFileSync, writeFileSync, mkdirSync } = require('fs');
const { once } = require('events');
const pixelmatch = require('pixelmatch');
const { PNG } = require('pngjs');
const {
Builder,
By,
Key,
until,
Capabilities,
logging,
} = require('selenium-webdriver');
const server = require('../../server');
async function test() {
let pixelDifference;
let error;
let capabilities;
switch (process.env.BROWSER) {
case 'ie':
capabilities = Capabilities.ie();
capabilities.set('ignoreProtectedModeSettings', true);
capabilities.set('ignoreZoomSetting', true);
capabilities.set('ie.options', {
enableFullPageScreenshot: true,
ensureCleanSession: true,
});
break;
case 'edge':
capabilities = Capabilities.edge();
break;
case 'safari':
capabilities = Capabilities.safari();
capabilities.set('safari.options', { technologyPreview: false });
break;
case 'firefox': {
capabilities = Capabilities.firefox().setLoggingPrefs({ browser: 'ALL' });
break;
}
case 'chrome': {
capabilities = Capabilities.chrome().setLoggingPrefs({ browser: 'ALL' });
capabilities.set('chromeOptions', {
args: ['--headless', '--no-sandbox', '--disable-gpu'],
});
break;
}
}
let driver = await new Builder().withCapabilities(capabilities).build();
if (!server.listening) await once(server, 'listening');
try {
await driver.get(`http://127.0.0.1:${server.address().port}`);
// wait for last choice to init
await driver.wait(
until.elementLocated(By.css('#reset-multiple ~ .choices__list')),
10000,
'waiting for all Choices instances to init',
);
// Resize window
await driver
.manage()
.window()
.maximize();
await driver
.manage()
.window()
// magic numbers here to make sure all demo page are fit inside
.setRect({ x: 0, y: 0, width: 630, height: 4000 });
// and click on press space on it, so it should open choices
await driver
.findElement(By.css('#reset-multiple ~ .choices__list button'))
.sendKeys(Key.SPACE);
await driver.sleep(1000);
// take screenshot
const image = await driver.takeScreenshot();
const imageBuffer = Buffer.from(image, 'base64');
const snapshotName = `${process.env.BROWSER}-${process.platform}.png`;
const artifactsPath = 'screenshot';
mkdirSync(artifactsPath, { recursive: true });
writeFileSync(path.join(artifactsPath, snapshotName), imageBuffer);
// compare with snapshot
const screenshot = PNG.sync.read(imageBuffer);
const snapshot = PNG.sync.read(
readFileSync(path.resolve(__dirname, `./__snapshots__/${snapshotName}`)),
);
const { width, height } = screenshot;
const diff = new PNG({ width, height });
pixelDifference = pixelmatch(
screenshot.data,
snapshot.data,
diff.data,
width,
height,
{
threshold: 1,
},
);
writeFileSync(path.join(artifactsPath, 'diff.png'), PNG.sync.write(diff));
// getting console logs
// ensure no errors in console (only supported in Chrome currently)
if (process.env.BROWSER === 'chrome') {
const entries = await driver
.manage()
.logs()
.get(logging.Type.BROWSER);
if (
Array.isArray(entries) &&
entries.some(entry => entry.level.name_ === 'SEVERE')
)
throw new Error(JSON.stringify(entries));
}
} catch (err) {
console.error(err);
error = err;
} finally {
await Promise.all([
driver.quit(),
new Promise(resolve => server.close(resolve)),
]);
}
if (pixelDifference > 200) {
console.error(
`Snapshot is different from screenshot by ${pixelDifference} pixels`,
);
process.exit(1);
}
if (error) process.exit(1);
}
process.on('unhandledRejection', err => {
console.error(err);
process.exit(1);
});
process.once('uncaughtException', err => {
console.error(err);
process.exit(1);
});
setImmediate(test);

View file

@ -1,29 +0,0 @@
name-template: 'Draft (next release)'
tag-template: 'v$NEXT_PATCH_VERSION'
sort-direction: descending
exclude-labels:
- 'skip-changelog'
- 'release'
categories:
- title: '🚨 Breaking changes'
labels:
- 'breaking change'
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'bugfix'
- title: '🔧 Maintenance'
labels:
- 'chore'
- 'housekeeping'
- 'refactor'
- 'documentation'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
template: |
# Changes
$CHANGES
# Contributors
$CONTRIBUTORS

View file

@ -1,131 +0,0 @@
name: Browsers
on:
pull_request:
paths:
- 'src/**'
- 'package-lock.json'
- '.browserslistrc'
- '.babelrc'
- 'webpack.config.*'
- 'public/index.html'
- '.github/actions-scripts/__snapshots__/**'
- '.github/workflows/browsers.yml'
jobs:
selenium:
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
browser: [edge, firefox, safari, chrome]
exclude:
- os: windows-latest
browser: safari
- os: macos-latest
browser: edge
- os: macos-latest
browser: chrome
# Safari workaround is not working in Catalina
- browser: safari
runs-on: ${{ matrix.os }}
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.OS }}-build-${{ matrix.browser }}
restore-keys: |
${{ runner.OS }}-build-${{ env.cache-name }}-
${{ runner.OS }}-build-
${{ runner.OS }}-
- run: |
npm ci
npm run build
env:
CYPRESS_INSTALL_BINARY: 0
HUSKY_SKIP_INSTALL: true
# install drivers
- name: Enable Safari Driver
run: |
# Workaround for `sudo safardriver --enable` not working:
# https://github.com/web-platform-tests/wpt/issues/19845
# https://github.com/web-platform-tests/wpt/blob/master/tools/ci/azure/install_safari.yml
mkdir -p ~/Library/WebDriver/
curl https://raw.githubusercontent.com/web-platform-tests/wpt/master/tools/ci/azure/com.apple.Safari.plist -o ~/Library/WebDriver/com.apple.Safari.plist
defaults write com.apple.Safari WebKitJavaScriptCanOpenWindowsAutomatically 1
# sudo safaridriver --enable
if: matrix.browser == 'safari'
- run: |
brew install --cask firefox
brew install geckodriver
if: matrix.browser == 'firefox' && matrix.os == 'macos-latest'
- run: echo "$env:GeckoWebDriver" >> $GITHUB_PATH
if: matrix.browser == 'firefox' && matrix.os == 'windows-latest'
- run: echo "$env:EdgeWebDriver" >> $GITHUB_PATH
if: matrix.browser == 'edge' && matrix.os == 'windows-latest'
- run: echo "$env:ChromeWebDriver" >> $GITHUB_PATH
if: matrix.browser == 'chrome' && matrix.os == 'windows-latest'
- run: npm i --no-optional --no-audit selenium-webdriver pixelmatch pngjs
- run: node .github/actions-scripts/selenium.js
env:
BROWSER: ${{ matrix.browser }}
PORT: 0
NODE_ENV: production # prevent watching
- uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshot-${{ matrix.browser }}-${{ matrix.os }}
path: screenshot
puppeteer:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.OS }}-build-puppeteer
restore-keys: |
${{ runner.OS }}-build-puppeteer
- run: |
npm ci
npm run build
env:
CYPRESS_INSTALL_BINARY: 0
HUSKY_SKIP_INSTALL: true
- run: npm i --no-optional --no-audit puppeteer pixelmatch pngjs
- run: node .github/actions-scripts/puppeteer.js
env:
PORT: 0
NODE_ENV: production # prevent watching
- uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshot-puppeteer-darwin
path: screenshot

View file

@ -1,60 +0,0 @@
name: Build and test
on:
push:
branches:
- master
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
- name: Build and 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
##
## Disabling for now. There does not appear to be a secure way to do this
## with protected branches. See discussion:
## https://github.community/t/how-to-push-to-protected-branches-in-a-github-action/16101
##
# - name: Commit built files
# run: |
# git config --local user.email "action@github.com"
# git config --local user.name "GitHub Action"
# git commit -m "Update build files 🏗" -a || echo "No changes to commit" && exit 0
# - name: Push changes
# uses: ad-m/github-push-action@master
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}
- 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}}

View file

@ -1,42 +0,0 @@
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@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
- 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

View file

@ -1,32 +0,0 @@
name: Deploy Pages
on:
release:
types: [published]
workflow_dispatch:
jobs:
deploy-gh-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
registry-url: https://registry.npmjs.org/
- name: Build
run: |
npm ci
npm run build
rm -rf public/test
env:
CYPRESS_INSTALL_BINARY: 0
HUSKY_SKIP_INSTALL: true
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: ./public

View file

@ -1,24 +0,0 @@
name: Publish to npm
on:
release:
types: [published]
jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
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}}

View file

@ -1,71 +0,0 @@
name: End-to-end tests
on:
pull_request:
paths:
- 'src/**'
- 'package-lock.json'
- '.browserslistrc'
- '.babelrc'
- 'webpack.config.*'
- 'public/test/**'
- 'cypress/**'
- '.github/workflows/e2e-tests.yml'
jobs:
test-e2e:
runs-on: ubuntu-latest
env:
CI: true
TERM: xterm-256color
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-build-${{ env.cache-name }}-
${{ runner.OS }}-build-
${{ runner.OS }}-
- name: Get Cypress info
id: cypress-info
run: |
echo ::set-output name=version::$(jq -r .devDependencies.cypress ./package.json)
echo ::set-output name=cache::$(npx cypress cache path)
env:
CYPRESS_INSTALL_BINARY: 0
- name: Cache Cypress cache
uses: actions/cache@v2
with:
path: ${{ steps.cypress-info.outputs.cache }}
key: ${{ runner.OS }}-cypress-${{ steps.cypress-info.outputs.version }}
restore-keys: |
${{ runner.OS }}-cypress-${{ steps.cypress-info.outputs.version }}
- name: Install dependencies
run: npm ci
env:
HUSKY_SKIP_INSTALL: true
- name: run Cypress (with or without recording)
# if we have ran out of free Cypress recordings, run Cypress with recording switched off
run: npm exec -- run-p --race start cypress:ci || npm exec -- run-p --race start cypress:run
env:
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 }}

View file

@ -1,41 +0,0 @@
name: Code linting
on:
pull_request:
paths:
- 'src/scripts/**'
- 'src/styles/**'
- package-lock.json
- '.browserslistrc'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
env:
CYPRESS_INSTALL_BINARY: 0
HUSKY_SKIP_INSTALL: true
- name: run eslint
run: npm run lint:js
## Can't use same eslint config for TypeScript and JavaScript
## TypeScript rules cause rule definition not found errors
## Can be re-enabled if this is resolved: https://github.com/eslint/eslint/issues/14851
# - name: Lint JS bundle
# run: |
# npm run js:build
# npx eslint --no-ignore ./public/assets/scripts/*.js
- name: run stylelint
run: npm run lint:scss

View file

@ -1,23 +0,0 @@
name: Polyfills documentation
on:
pull_request:
paths:
- 'README.md'
- '.browserslistrc'
- '.eslintrc.json'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
- name: Check Polyfills documentation and settings sync
run: node .github/actions-scripts/polyfills-sync.js

View file

@ -1,14 +0,0 @@
name: Release drafter
on:
push:
branches:
- master
jobs:
update-draft-release:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,42 +0,0 @@
name: Unit tests
on:
pull_request:
paths:
- 'src/scripts/**'
- package-lock.json
- '.browserslistrc'
jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-node@v2
with:
node-version: 18
- 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}}

7
.gitignore vendored
View file

@ -1,12 +1,7 @@
node_modules
npm-debug.log
.DS_Store
.idea
# Test
tests/reports
tests/results
.nyc_output
coverage
cypress/videos
cypress/screenshots
tests/results

View file

@ -1,6 +0,0 @@
{
"skipCI": true,
"hooks": {
"pre-commit": "lint-staged"
}
}

View file

@ -1,8 +0,0 @@
require:
- 'ts-node/register'
- './config/jsdom.js'
exit: true
spec: src/**/**/*.test.ts
extension:
- ts
- js

2
.npmrc
View file

@ -1,2 +0,0 @@
message=":bookmark: Version %s"
git-tag-version=true

1
.nvmrc
View file

@ -1 +0,0 @@
v12.13.1

View file

@ -1,20 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"overrides": [
{
"files": ["*.svg"],
"options": {
"parser": "html",
"htmlWhitespaceSensitivity": "ignore"
}
},
{
"files": ["public/*.html"],
"options": {
"trailingComma": "es5"
}
}
]
}

View file

@ -1,6 +0,0 @@
{
"extends": "stylelint-config-standard-scss",
"rules": {
"declaration-block-no-redundant-longhand-properties": null
}
}

9
.travis.yml Normal file
View file

@ -0,0 +1,9 @@
language: node_js
node_js:
- "0.10"
before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
- npm install -g npm@latest
install:
- npm install
script: npm run js:test

View file

@ -1,17 +0,0 @@
{
// 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
View file

@ -1,70 +0,0 @@
{
"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:*"
}
}
]
}

72
.vscode/settings.json vendored
View file

@ -1,72 +0,0 @@
{
"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"
}
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"stylelint.validate": [
"css",
"less",
"postcss",
"scss"
]
}

87
.vscode/tasks.json vendored
View file

@ -1,87 +0,0 @@
{
// 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
}
]
}

View file

@ -1,46 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at josh@joshuajohnson.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View file

@ -1,24 +0,0 @@
# 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: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 |
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
## Pull requests
When submitting a pull request that resolves a bug, feel free to use the following template:
```md
## This is the problem:
## Steps to reproduce:
## This is my solution:
```

944
README.md

File diff suppressed because it is too large Load diff

View file

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

View file

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

View file

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/assets/images/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/assets/images/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>

View file

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 574 B

View file

Before

Width:  |  Height:  |  Size: 887 B

After

Width:  |  Height:  |  Size: 887 B

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 6 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

5505
assets/scripts/dist/choices.js vendored Normal file

File diff suppressed because it is too large Load diff

1
assets/scripts/dist/choices.js.map vendored Normal file

File diff suppressed because one or more lines are too long

4
assets/scripts/dist/choices.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"version":3,"file":"choices.min.js","sources":[],"mappings":";;","sourceRoot":""}

View file

@ -0,0 +1,73 @@
export const addItem = (value, label, id, choiceId, groupId) => {
return {
type: 'ADD_ITEM',
value,
label,
id,
choiceId,
groupId,
};
};
export const removeItem = (id, choiceId) => {
return {
type: 'REMOVE_ITEM',
id,
choiceId,
};
};
export const highlightItem = (id, highlighted) => {
return {
type: 'HIGHLIGHT_ITEM',
id,
highlighted,
};
};
export const addChoice = (value, label, id, groupId, disabled) => {
return {
type: 'ADD_CHOICE',
value,
label,
id,
groupId,
disabled,
};
};
export const filterChoices = (results) => {
return {
type: 'FILTER_CHOICES',
results,
};
};
export const activateChoices = (active = true) => {
return {
type: 'ACTIVATE_CHOICES',
active,
};
};
export const clearChoices = () => {
return {
type: 'CLEAR_CHOICES',
};
};
export const addGroup = (value, id, active, disabled) => {
return {
type: 'ADD_GROUP',
value,
id,
active,
disabled,
};
};
export const clearAll = () => {
return {
type: 'CLEAR_ALL',
};
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,129 @@
/* eslint-disable */
(function () {
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = (function() {
var toStr = Object.prototype.toString;
var isCallable = function(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function(value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */ ) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
// Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
function CustomEvent (event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();

View file

@ -0,0 +1,506 @@
/* eslint-disable */
/**
* Capitalises the first letter of each word in a string
* @param {String} str String to capitalise
* @return {String} Capitalised string
*/
export const capitalise = function(str) {
return str.replace(/\w\S*/g, function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
/**
* Tests the type of an object
* @param {String} type Type to test object against
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const isType = function(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
};
/**
* Tests to see if a passed object is a node
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const isNode = (o) => {
return (
typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"
);
};
/**
* Tests to see if a passed object is an element
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const isElement = (o) => {
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"
);
};
/**
* Merges unspecified amount of objects into new object
* @private
* @return {Object} Merged object of arguments
*/
export const extend = function() {
let extended = {};
let length = arguments.length;
/**
* Merge one object into another
* @param {Object} obj Object to merge into extended object
*/
let merge = function(obj) {
for (let prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// If deep merge and property is an object, merge properties
if (isType('Object', obj[prop])) {
extended[prop] = extend(true, extended[prop], obj[prop]);
} else {
extended[prop] = obj[prop];
}
}
}
};
// Loop through each passed argument
for (let i = 0; i < length; i++) {
// store argument at position i
let obj = arguments[i];
// If we are in fact dealing with an object, merge it.
if (isType('Object', obj)) {
merge(obj);
}
}
return extended;
};
/**
* CSS transition end event listener
* @return
*/
export const whichTransitionEvent = function() {
var t,
el = document.createElement("fakeelement");
var transitions = {
"transition": "transitionend",
"OTransition": "oTransitionEnd",
"MozTransition": "transitionend",
"WebkitTransition": "webkitTransitionEnd"
}
for (t in transitions) {
if (el.style[t] !== undefined) {
return transitions[t];
}
}
};
/**
* CSS animation end event listener
* @return
*/
export const whichAnimationEvent = function() {
var t,
el = document.createElement('fakeelement');
var animations = {
'animation': 'animationend',
'OAnimation': 'oAnimationEnd',
'MozAnimation': 'animationend',
'WebkitAnimation': 'webkitAnimationEnd'
};
for (t in animations) {
if (el.style[t] !== undefined) {
return animations[t];
}
}
};
/**
* Get the ancestors of each element in the current set of matched elements,
* up to but not including the element matched by the selector
* @param {NodeElement} elem Element to begin search from
* @param {NodeElement} parent Parent to find
* @param {String} selector Class to find
* @return {Array} Array of parent elements
*/
export const getParentsUntil = function(elem, parent, selector) {
var parents = [];
// Get matches
for (; elem && elem !== document; elem = elem.parentNode) {
// Check if parent has been reached
if (parent) {
var parentType = parent.charAt(0);
// If parent is a class
if (parentType === '.') {
if (elem.classList.contains(parent.substr(1))) {
break;
}
}
// If parent is an ID
if (parentType === '#') {
if (elem.id === parent.substr(1)) {
break;
}
}
// If parent is a data attribute
if (parentType === '[') {
if (elem.hasAttribute(parent.substr(1, parent.length - 1))) {
break;
}
}
// If parent is a tag
if (elem.tagName.toLowerCase() === parent) {
break;
}
}
if (selector) {
var selectorType = selector.charAt(0);
// If selector is a class
if (selectorType === '.') {
if (elem.classList.contains(selector.substr(1))) {
parents.push(elem);
}
}
// If selector is an ID
if (selectorType === '#') {
if (elem.id === selector.substr(1)) {
parents.push(elem);
}
}
// If selector is a data attribute
if (selectorType === '[') {
if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
parents.push(elem);
}
}
// If selector is a tag
if (elem.tagName.toLowerCase() === selector) {
parents.push(elem);
}
} else {
parents.push(elem);
}
}
// Return parents if any exist
if (parents.length === 0) {
return null;
} else {
return parents;
}
};
export const wrap = function(element, wrapper) {
wrapper = wrapper || document.createElement('div');
if (element.nextSibling) {
element.parentNode.insertBefore(wrapper, element.nextSibling);
} else {
element.parentNode.appendChild(wrapper);
}
return wrapper.appendChild(element);
};
export const getSiblings = function(elem) {
var siblings = [];
var sibling = elem.parentNode.firstChild;
for (; sibling; sibling = sibling.nextSibling) {
if (sibling.nodeType === 1 && sibling !== elem) {
siblings.push(sibling);
}
}
return siblings;
};
/**
* Find ancestor in DOM tree
* @param {NodeElement} el Element to start search from
* @param {[type]} cls Class of parent
* @return {NodeElement} Found parent element
*/
export const findAncestor = function(el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
};
/**
* Debounce an event handler.
* @param {Function} func Function to run after wait
* @param {Number} wait The delay before the function is executed
* @param {Boolean} immediate If passed, trigger the function on the leading edge, instead of the trailing.
* @return {Function} A function will be called after it stops being called for a given delay
*/
export const debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
/**
* Get an element's distance from the top of the page
* @private
* @param {NodeElement} el Element to test for
* @return {Number} Elements Distance from top of page
*/
export const getElemDistance = function(el) {
var location = 0;
if (el.offsetParent) {
do {
location += el.offsetTop;
el = el.offsetParent;
} while (el);
}
return location >= 0 ? location : 0;
};
/**
* Determine element height multiplied by any offsets
* @private
* @param {HTMLElement} el Element to test for
* @return {Number} Height of element
*/
export const getElementOffset = function(el, offset) {
var elOffset = offset;
if (elOffset > 1) elOffset = 1;
if (elOffset > 0) elOffset = 0;
return Math.max(el.offsetHeight * elOffset);
};
/**
* Get the next or previous element from a given start point
* @param {HTMLElement} startEl Element to start position from
* @param {String} className The class we will look through
* @param {Number} direction Positive next element, negative previous element
* @return {[HTMLElement} Found element
*/
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];
};
/**
* Get scroll position based on top/bottom position
* @private
* @return {String} Position of scroll
*/
export const getScrollPosition = function(position) {
if (position === 'bottom') {
// Scroll position from the bottom of the viewport
return Math.max((window.scrollY || window.pageYOffset) + (window.innerHeight || document.documentElement.clientHeight));
} else {
// Scroll position from the top of the viewport
return (window.scrollY || window.pageYOffset);
}
};
/**
* Determine whether an element is within the viewport
* @param {HTMLElement} el Element to test
* @return {String} Position of scroll
* @return {Boolean}
*/
export const isInView = function(el, position, offset) {
// If the user has scrolled further than the distance from the element to the top of its parent
return this.getScrollPosition(position) > (this.getElemDistance(el) + this.getElementOffset(el, offset)) ? true : false;
};
/**
* Determine whether an element is within
* @param {HTMLElement} el Element to test
* @param {HTMLElement} parent Scrolling parent
* @param {Number} direction Whether element is visible from above or below
* @return {Boolean}
*/
export const isScrolledIntoView = (el, parent, direction = 1) => {
if (!el) return;
let isVisible;
if (direction > 0) {
// In view from bottom
isVisible = (parent.scrollTop + parent.offsetHeight) >= (el.offsetTop + el.offsetHeight);
} else {
// In view from top
isVisible = el.offsetTop >= parent.scrollTop;
}
return isVisible;
};
/**
* Remove html tags from a string
* @param {String} Initial string/html
* @return {String} Sanitised string
*/
export const stripHTML = function(html) {
let el = document.createElement("DIV");
el.innerHTML = html;
return el.textContent || el.innerText || "";
};
/**
* Adds animation to an element and removes it upon animation completion
* @param {Element} el Element to add animation to
* @param {String} animation Animation class to add to element
* @return
*/
export const addAnimation = (el, animation) => {
let animationEvent = whichAnimationEvent();
let removeAnimation = () => {
el.classList.remove(animation);
el.removeEventListener(animationEvent, removeAnimation, false);
};
el.classList.add(animation);
el.addEventListener(animationEvent, removeAnimation, false);
};
/**
* Get a random number between a range
* @param {Number} min Minimum range
* @param {Number} max Maximum range
* @return {Number} Random number
*/
export const getRandomNumber = function(min, max) {
return Math.floor(Math.random() * (max - min) + min);
};
/**
* Turn a string into a node
* @param {String} String to convert
* @return {HTMLElement} Converted node element
*/
export const strToEl = (function() {
var tmpEl = document.createElement('div');
return function(str) {
var r;
tmpEl.innerHTML = str;
r = tmpEl.children[0];
while (tmpEl.firstChild) {
tmpEl.removeChild(tmpEl.firstChild);
}
return r;
};
}());
/**
* Sets the width of a passed input based on its value
* @return {Number} Width of input
*/
export const getWidthOfInput = (input) => {
const value = input.value || input.placeholder;
let width = input.offsetWidth;
if (value) {
const testEl = strToEl(`<span>${ 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';
document.body.appendChild(testEl);
if (value && testEl.offsetWidth !== input.offsetWidth) {
width = testEl.offsetWidth + 4;
}
document.body.removeChild(testEl);
}
return `${width}px`;
};
/**
* Sorting function for current and previous string
* @param {String} a Current value
* @param {String} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
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;
};
/**
* Sort by numeric score
* @param {Object} a Current value
* @param {Object} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
export const sortByScore = (a, b) => {
return a.score - b.score;
};
/**
* Trigger native event
* @param {NodeElement} element Element to trigger event on
* @param {String} type Type of event to trigger
* @param {Object} customArgs Data to pass with event
* @return {Object} Triggered event
*/
export const triggerEvent = (element, type, customArgs = null) => {
const event = new CustomEvent(type, {
detail: customArgs,
bubbles: true,
cancelable: true
});
return element.dispatchEvent(event);
};

View file

@ -0,0 +1,97 @@
const choices = (state = [], action) => {
switch (action.type) {
case 'ADD_CHOICE': {
/*
A disabled choice appears in the choice dropdown but cannot be selected
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return [...state, {
id: action.id,
groupId: action.groupId,
value: action.value,
label: action.label,
disabled: action.disabled,
selected: false,
active: true,
score: 9999,
}];
}
case 'ADD_ITEM': {
let newState = state;
// If all choices need to be activated
if (action.activateOptions) {
newState = state.map((choice) => {
choice.active = action.active;
return choice;
});
}
// When an item is added and it has an associated choice,
// we want to disable it so it can't be chosen again
if (action.choiceId > -1) {
newState = state.map((choice) => {
if (choice.id === parseInt(action.choiceId, 10)) {
choice.selected = true;
}
return choice;
});
}
return newState;
}
case 'REMOVE_ITEM': {
// When an item is removed and it has an associated choice,
// we want to re-enable it so it can be chosen again
if (action.choiceId > -1) {
return state.map((choice) => {
if (choice.id === parseInt(action.choiceId, 10)) {
choice.selected = false;
}
return choice;
});
}
return state;
}
case 'FILTER_CHOICES': {
const filteredResults = action.results;
const filteredState = state.map((choice) => {
// Set active state based on whether choice is
// within filtered results
choice.active = filteredResults.some((result) => {
if (result.item.id === choice.id) {
choice.score = result.score;
return true;
}
return false;
});
return choice;
});
return filteredState;
}
case 'ACTIVATE_CHOICES': {
return state.map((choice) => {
choice.active = action.active;
return choice;
});
}
case 'CLEAR_CHOICES': {
return state.choices = [];
}
default: {
return state;
}
}
};
export default choices;

View file

@ -0,0 +1,22 @@
const groups = (state = [], action) => {
switch (action.type) {
case 'ADD_GROUP': {
return [...state, {
id: action.id,
value: action.value,
active: action.active,
disabled: action.disabled,
}];
}
case 'CLEAR_CHOICES': {
return state.groups = [];
}
default: {
return state;
}
}
};
export default groups;

View file

@ -2,33 +2,21 @@ import { combineReducers } from 'redux';
import items from './items';
import groups from './groups';
import choices from './choices';
import loading from './loading';
import { cloneObject } from '../lib/utils';
export const defaultState = {
groups: [],
items: [],
choices: [],
loading: false,
};
const appReducer = combineReducers({
items,
groups,
choices,
loading,
});
const rootReducer = (passedState, action): object => {
const rootReducer = (passedState, action) => {
let state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === 'CLEAR_ALL') {
state = defaultState;
} else if (action.type === 'RESET_TO') {
return cloneObject(action.state);
state = undefined;
}
return appReducer(state, action);

View file

@ -0,0 +1,48 @@
const items = (state = [], action) => {
switch (action.type) {
case 'ADD_ITEM': {
// Add object to items array
const newState = [...state, {
id: action.id,
choiceId: action.choiceId,
groupId: action.groupId,
value: action.value,
label: action.label,
active: true,
highlighted: false,
}];
return newState.map((item) => {
if (item.highlighted) {
item.highlighted = false;
}
return item;
});
}
case 'REMOVE_ITEM': {
// Set item to inactive
return state.map((item) => {
if (item.id === action.id) {
item.active = false;
}
return item;
});
}
case 'HIGHLIGHT_ITEM': {
return state.map((item) => {
if (item.id === action.id) {
item.highlighted = action.highlighted;
}
return item;
});
}
default: {
return state;
}
}
};
export default items;

View file

@ -0,0 +1,164 @@
import { createStore } from 'redux';
import rootReducer from './../reducers/index.js';
export default class Store {
constructor() {
this.store = createStore(
rootReducer
, window.devToolsExtension ? window.devToolsExtension() : undefined
);
}
/**
* Get store object (wrapping Redux method)
* @return {Object} State
*/
getState() {
return this.store.getState();
}
/**
* Dispatch event to store (wrapped Redux method)
* @param {Function} action Action function to trigger
* @return
*/
dispatch(action) {
this.store.dispatch(action);
}
/**
* Subscribe store to function call (wrapped Redux method)
* @param {Function} onChange Function to trigger when state changes
* @return
*/
subscribe(onChange) {
this.store.subscribe(onChange);
}
/**
* Get items from store
* @return {Array} Item objects
*/
getItems() {
const state = this.store.getState();
return state.items;
}
/**
* Get active items from store
* @return {Array} Item objects
*/
getItemsFilteredByActive() {
const items = this.getItems();
const values = items.filter((item) => {
return item.active === true;
}, []);
return values;
}
/**
* Get items from store reduced to just their values
* @return {Array} Item objects
*/
getItemsReducedToValues(items = this.getItems()) {
const values = items.reduce((prev, current) => {
prev.push(current.value);
return prev;
}, []);
return values;
}
/**
* Get choices from store
* @return {Array} Option objects
*/
getChoices() {
const state = this.store.getState();
return state.choices;
}
/**
* Get active choices from store
* @return {Array} Option objects
*/
getChoicesFilteredByActive() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.active === true;
}, []);
return values;
}
/**
* Get selectable choices from store
* @return {Array} Option objects
*/
getChoicesFilteredBySelectable() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.disabled !== true;
}, []);
return values;
}
/**
* Get single choice by it's ID
* @return {Object} Found choice
*/
getChoiceById(id) {
if (id) {
const choices = this.getChoicesFilteredByActive();
const foundChoice = choices.find((choice) => choice.id === parseInt(id, 10));
return foundChoice;
}
return false;
}
/**
* Get groups from store
* @return {Array} Group objects
*/
getGroups() {
const state = this.store.getState();
return state.groups;
}
/**
* Get active groups from store
* @return {Array} Group objects
*/
getGroupsFilteredByActive() {
const groups = this.getGroups();
const choices = this.getChoices();
const values = groups.filter((group) => {
const isActive = group.active === true && group.disabled === false;
const hasActiveOptions = choices.some((choice) => {
return choice.active === true && choice.disabled === false;
});
return isActive && hasActiveOptions;
}, []);
return values;
}
/**
* Get group by group id
* @param {Number} id Group ID
* @return {Object} Group data
*/
getGroupById(id) {
const groups = this.getGroups();
const foundGroup = groups.find((group) => {
return group.id === id;
});
return foundGroup;
}
}
module.exports = Store;

View file

@ -1,19 +1,16 @@
/* =============================================
/*=============================================
= Generic styling =
============================================= */
=============================================*/
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
*,
*::before,
*::after {
*, *:before, *:after {
box-sizing: border-box;
}
html,
body {
html, body {
position: relative;
margin: 0;
width: 100%;
@ -24,7 +21,7 @@ body {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 16px;
line-height: 1.4;
color: #fff;
color: #FFFFFF;
background-color: #333;
overflow-x: hidden;
}
@ -39,33 +36,25 @@ label {
p {
margin-top: 0;
margin-bottom: 8px;
}
hr {
display: block;
margin: 30px 0;
margin: 36px 0;
border: 0;
border-bottom: 1px solid #eaeaea;
height: 1px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 12px;
font-weight: 400;
line-height: 1.2;
}
a,
a:visited,
a:focus {
color: #fff;
a, a:visited, a:focus {
color: #FFFFFF;
text-decoration: none;
font-weight: 600;
}
@ -79,50 +68,42 @@ a:focus {
border-radius: 2.5px;
font-size: 14px;
-webkit-appearance: none;
appearance: none;
-moz-appearance: none;
appearance: none;
margin-bottom: 24px;
}
h1,
.h1 {
h1, .h1 {
font-size: 32px;
}
h2,
.h2 {
h2, .h2 {
font-size: 24px;
}
h3,
.h3 {
h3, .h3 {
font-size: 20px;
}
h4,
.h4 {
h4, .h4 {
font-size: 18px;
}
h5,
.h5 {
h5, .h5 {
font-size: 16px;
}
h6,
.h6 {
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;
@ -130,13 +111,12 @@ label + p {
}
.section {
background-color: #fff;
background-color: #FFFFFF;
padding: 24px;
color: #333;
}
.section a,
.section a:visited,
.section a:focus {
.section a, .section a:visited, .section a:focus {
color: #00bcd4;
}
@ -145,7 +125,7 @@ label + p {
margin-bottom: 12px;
}
.logo-img {
.logo__img {
width: 100%;
height: auto;
display: inline-block;
@ -158,24 +138,4 @@ label + p {
display: none;
}
.push-bottom {
margin-bottom: 24px;
}
.zero-bottom {
margin-bottom: 0;
}
.zero-top {
margin-top: 0;
}
.text-center {
text-align: center;
}
[data-test-hook] {
margin-bottom: 24px;
}
/* ===== End of Section comment block ====== */
/*===== End of Section comment block ======*/

1
assets/styles/css/base.min.css vendored Normal file
View file

@ -0,0 +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{margin-bottom:8px;font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:36px 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}.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}

View file

@ -1,53 +1,55 @@
/* ===============================
/*===============================
= Choices =
=============================== */
===============================*/
.choices {
position: relative;
overflow: hidden;
margin-bottom: 24px;
font-size: 16px;
}
.choices:focus {
outline: none;
}
.choices:last-child {
margin-bottom: 0;
}
.choices.is-open {
overflow: visible;
}
.choices.is-disabled .choices__inner,
.choices.is-disabled .choices__input {
background-color: #eaeaea;
.choices.is-disabled .choices__inner, .choices.is-disabled .choices__input {
background-color: #EAEAEA;
cursor: not-allowed;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.choices.is-disabled .choices__item {
cursor: not-allowed;
}
.choices [hidden] {
display: none !important;
}
.choices[data-type*=select-one] {
.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 #ddd;
background-color: #fff;
border-bottom: 1px solid #DDDDDD;
background-color: #FFFFFF;
margin: 0;
}
.choices[data-type*=select-one] .choices__button {
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
.choices[data-type*="select-one"] .choices__button {
background-image: url("../../icons//cross-inverse.svg");
padding: 0;
background-size: 8px;
height: 100%;
position: absolute;
top: 50%;
right: 0;
@ -56,23 +58,23 @@
height: 20px;
width: 20px;
border-radius: 10em;
opacity: 0.25;
opacity: .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: 0 0 0 2px #00bcd4;
.choices[data-type*="select-one"] .choices__button:focus {
box-shadow: 0px 0px 0px 2px #00BCD4;
}
.choices[data-type*=select-one] .choices__item[data-value=""] .choices__button {
display: none;
}
.choices[data-type*=select-one]::after {
.choices[data-type*="select-one"]:after {
content: "";
height: 0;
width: 0;
border-style: solid;
border-color: #333 transparent transparent transparent;
border-color: #333333 transparent transparent transparent;
border-width: 5px;
position: absolute;
right: 11.5px;
@ -80,27 +82,29 @@
margin-top: -2.5px;
pointer-events: none;
}
.choices[data-type*=select-one].is-open::after {
border-color: transparent transparent #333 transparent;
.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;
@ -109,16 +113,14 @@
margin-left: 8px;
padding-left: 16px;
border-left: 1px solid #008fa1;
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
background-image: url("../../icons//cross.svg");
background-size: 8px;
width: 8px;
line-height: 1;
opacity: 0.75;
border-radius: 0;
opacity: .75;
}
.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;
}
@ -128,18 +130,21 @@
width: 100%;
background-color: #f9f9f9;
padding: 7.5px 7.5px 3.75px;
border: 1px solid #ddd;
border: 1px solid #DDDDDD;
border-radius: 2.5px;
font-size: 14px;
min-height: 44px;
overflow: hidden;
}
.is-focused .choices__inner, .is-open .choices__inner {
border-color: #b7b7b7;
}
.is-open .choices__inner {
border-radius: 2.5px 2.5px 0 0;
}
.is-flipped.is-open .choices__inner {
border-radius: 0 0 2.5px 2.5px;
}
@ -149,15 +154,18 @@
padding-left: 0;
list-style: none;
}
.choices__list--single {
display: inline-block;
padding: 4px 16px 4px 4px;
width: 100%;
}
[dir=rtl] .choices__list--single {
[dir="rtl"] .choices__list--single {
padding-right: 4px;
padding-left: 16px;
}
.choices__list--single .choices__item {
width: 100%;
}
@ -165,6 +173,7 @@
.choices__list--multiple {
display: inline;
}
.choices__list--multiple .choices__item {
display: inline-block;
vertical-align: middle;
@ -174,99 +183,111 @@
font-weight: 500;
margin-right: 3.75px;
margin-bottom: 3.75px;
background-color: #00bcd4;
background-color: #00BCD4;
border: 1px solid #00a5bb;
color: #fff;
color: #FFFFFF;
word-break: break-all;
box-sizing: border-box;
}
.choices__list--multiple .choices__item[data-deletable] {
padding-right: 5px;
}
[dir=rtl] .choices__list--multiple .choices__item {
[dir="rtl"] .choices__list--multiple .choices__item {
margin-right: 0;
margin-left: 3.75px;
}
.choices__list--multiple .choices__item.is-highlighted {
background-color: #00a5bb;
border: 1px solid #008fa1;
}
.is-disabled .choices__list--multiple .choices__item {
background-color: #aaaaaa;
border: 1px solid #919191;
}
.choices__list--dropdown, .choices__list[aria-expanded] {
visibility: hidden;
.choices__list--dropdown {
display: none;
z-index: 1;
position: absolute;
width: 100%;
background-color: #fff;
border: 1px solid #ddd;
background-color: #FFFFFF;
border: 1px solid #DDDDDD;
top: 100%;
margin-top: -1px;
border-bottom-left-radius: 2.5px;
border-bottom-right-radius: 2.5px;
overflow: hidden;
word-break: break-all;
will-change: visibility;
}
.is-active.choices__list--dropdown, .is-active.choices__list[aria-expanded] {
visibility: visible;
.choices__list--dropdown.is-active {
display: block;
}
.is-open .choices__list--dropdown, .is-open .choices__list[aria-expanded] {
.is-open .choices__list--dropdown {
border-color: #b7b7b7;
}
.is-flipped .choices__list--dropdown, .is-flipped .choices__list[aria-expanded] {
.is-flipped .choices__list--dropdown {
top: auto;
bottom: 100%;
margin-top: 0;
margin-bottom: -1px;
border-radius: 0.25rem 0.25rem 0 0;
border-radius: .25rem .25rem 0 0;
}
.choices__list--dropdown .choices__list, .choices__list[aria-expanded] .choices__list {
.choices__list--dropdown .choices__list {
position: relative;
max-height: 300px;
overflow: auto;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
}
.choices__list--dropdown .choices__item, .choices__list[aria-expanded] .choices__item {
.choices__list--dropdown .choices__item {
position: relative;
padding: 10px;
font-size: 14px;
}
[dir=rtl] .choices__list--dropdown .choices__item, [dir=rtl] .choices__list[aria-expanded] .choices__item {
[dir="rtl"] .choices__list--dropdown .choices__item {
text-align: right;
}
@media (min-width: 640px) {
.choices__list--dropdown .choices__item--selectable, .choices__list[aria-expanded] .choices__item--selectable {
.choices__list--dropdown .choices__item--selectable {
padding-right: 100px;
}
.choices__list--dropdown .choices__item--selectable::after, .choices__list[aria-expanded] .choices__item--selectable::after {
.choices__list--dropdown .choices__item--selectable:after {
content: attr(data-select-text);
font-size: 12px;
opacity: 0;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
}
[dir=rtl] .choices__list--dropdown .choices__item--selectable, [dir=rtl] .choices__list[aria-expanded] .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[aria-expanded] .choices__item--selectable::after {
[dir="rtl"] .choices__list--dropdown .choices__item--selectable:after {
right: auto;
left: 10px;
}
}
.choices__list--dropdown .choices__item--selectable.is-highlighted, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
.choices__list--dropdown .choices__item--selectable.is-highlighted {
background-color: #f2f2f2;
}
.choices__list--dropdown .choices__item--selectable.is-highlighted::after, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after {
opacity: 0.5;
.choices__list--dropdown .choices__item--selectable.is-highlighted:after {
opacity: .5;
}
.choices__item {
@ -280,8 +301,10 @@
.choices__item--disabled {
cursor: not-allowed;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
opacity: 0.5;
opacity: .5;
}
.choices__heading {
@ -295,13 +318,15 @@
.choices__button {
text-indent: -9999px;
-webkit-appearance: none;
appearance: none;
-moz-appearance: none;
appearance: none;
border: 0;
background-color: transparent;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.choices__button:focus {
outline: none;
}
@ -317,24 +342,18 @@
max-width: 100%;
padding: 4px 0 4px 2px;
}
.choices__input:focus {
outline: 0;
}
.choices__input::-webkit-search-decoration, .choices__input::-webkit-search-cancel-button, .choices__input::-webkit-search-results-button, .choices__input::-webkit-search-results-decoration {
display: none;
}
.choices__input::-ms-clear, .choices__input::-ms-reveal {
display: none;
width: 0;
height: 0;
}
[dir=rtl] .choices__input {
[dir="rtl"] .choices__input {
padding-right: 2px;
padding-left: 0;
}
.choices__placeholder {
opacity: 0.5;
opacity: .5;
}
/* ===== End of Choices ====== */
/*===== End of Choices ======*/

1
assets/styles/css/choices.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,122 @@
$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;
/*=============================================
= Generic styling =
=============================================*/
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}
*, *:before, *:after {
box-sizing: border-box
}
html, body {
position: relative;
margin: 0;
width: 100%;
height: 100%;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 16px;
line-height: 1.4;
color: #FFFFFF;
background-color: #333;
overflow-x: hidden;
}
label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
}
p { margin-top: 0; }
hr {
display: block;
margin: $global-guttering*1.5 0;
border: 0;
border-bottom: 1px solid #eaeaea;
height: 1px;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: $global-guttering/2;
font-weight: 400;
line-height: 1.2;
}
a, a:visited, a:focus {
color: #FFFFFF;
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;
appearance: none;
margin-bottom: $global-guttering;
}
h1, .h1 { font-size: $global-font-size-h1; }
h2, .h2 { font-size: $global-font-size-h2; }
h3, .h3 { font-size: $global-font-size-h3; }
h4, .h4 { font-size: $global-font-size-h4; }
h5, .h5 { font-size: $global-font-size-h5; }
h6, .h6 { font-size: $global-font-size-h6; }
.container {
display: block;
margin: auto;
max-width: 40em;
padding: $global-guttering*2;
@media (max-width: 620px) { padding: 0; }
}
.section {
background-color: #FFFFFF;
padding: $global-guttering;
color: #333;
a, a:visited, a:focus { color: #00bcd4; }
}
.logo {
display: block;
margin-bottom: $global-guttering/2;
}
.logo__img {
width: 100%;
height: auto;
display: inline-block;
max-width: 100%;
vertical-align: top;
padding: $global-guttering/4 0;
}
.visible-ie { display: none; }
/*===== End of Section comment block ======*/

View file

@ -1,10 +1,8 @@
/* ===============================
/*===============================
= Choices =
=============================== */
===============================*/
@use "sass:color";
$choices-selector: "choices" !default;
$choices-selector: 'choices' !default;
$choices-font-size-lg: 16px !default;
$choices-font-size-md: 14px !default;
$choices-font-size-sm: 12px !default;
@ -12,71 +10,49 @@ $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: #fff !default;
$choices-text-color: #333 !default;
$choices-keyline-color: #ddd !default;
$choices-primary-color: #00bcd4 !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-disabled-color: #eaeaea !default;
$choices-highlight-color: $choices-primary-color !default;
$choices-button-icon-path: '../../icons/' !default;
$choices-button-dimension: 8px !default;
$choices-button-offset: 8px !default;
$choices-icon-cross: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==") !default;
$choices-icon-cross-inverse: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==") !default;
$choices-z-index: 1 !default;
.#{$choices-selector} {
position: relative;
overflow: hidden;
margin-bottom: $choices-guttering;
font-size: $choices-font-size-lg;
&:focus {
outline: none;
}
&:last-child {
margin-bottom: 0;
}
&.is-open {
overflow: visible;
}
&:focus { outline: none; }
&:last-child { margin-bottom: 0; }
&.is-disabled {
.#{$choices-selector}__inner,
.#{$choices-selector}__input {
.#{$choices-selector}__inner, .#{$choices-selector}__input {
background-color: $choices-bg-color-disabled;
cursor: not-allowed;
user-select: none;
}
.#{$choices-selector}__item {
cursor: not-allowed;
}
}
[hidden] {
display: none !important;
.#{$choices-selector}__item { cursor: not-allowed; }
}
}
.#{$choices-selector}[data-type*='select-one'] {
.#{$choices-selector}[data-type*="select-one"] {
cursor: pointer;
.#{$choices-selector}__inner {
padding-bottom: 7.5px;
}
.#{$choices-selector}__inner { padding-bottom: 7.5px; }
.#{$choices-selector}__input {
display: block;
width: 100%;
padding: 10px;
border-bottom: 1px solid $choices-keyline-color;
background-color: #fff;
background-color: #FFFFFF;
margin: 0;
}
.#{$choices-selector}__button {
background-image: $choices-icon-cross-inverse;
background-image: url($choices-button-icon-path + '/cross-inverse.svg');
padding: 0;
background-size: 8px;
height: 100%;
position: absolute;
top: 50%;
right: 0;
@ -85,22 +61,11 @@ $choices-z-index: 1 !default;
height: 20px;
width: 20px;
border-radius: 10em;
opacity: 0.25;
&:hover,
&:focus {
opacity: 1;
}
&:focus {
box-shadow: 0 0 0 2px $choices-highlight-color;
}
opacity: .5;
&:hover, &:focus { opacity: 1; }
&:focus { box-shadow: 0px 0px 0px 2px $choices-highlight-color; }
}
.#{$choices-selector}__item[data-value=''] .#{$choices-selector}__button {
display: none;
}
&::after {
&:after {
content: "";
height: 0;
width: 0;
@ -113,14 +78,12 @@ $choices-z-index: 1 !default;
margin-top: -2.5px;
pointer-events: none;
}
&.is-open::after {
&.is-open:after {
border-color: transparent transparent $choices-text-color transparent;
margin-top: -7.5px;
}
&[dir="rtl"] {
&::after {
&:after {
left: 11.5px;
right: auto;
}
@ -133,31 +96,23 @@ $choices-z-index: 1 !default;
}
}
.#{$choices-selector}[data-type*='select-multiple'],
.#{$choices-selector}[data-type*='text'] {
.#{$choices-selector}__inner {
cursor: text;
}
.#{$choices-selector}[data-type*="select-multiple"], .#{$choices-selector}[data-type*="text"] {
.#{$choices-selector}__inner { cursor: text; }
.#{$choices-selector}__button {
position: relative;
display: inline-block;
margin-top: 0;
margin-right: -$choices-button-offset * 0.5;
margin-right: -$choices-button-offset/2;
margin-bottom: 0;
margin-left: $choices-button-offset;
padding-left: $choices-button-offset * 2;
border-left: 1px solid color.adjust($choices-primary-color, $lightness: -10%);
background-image: $choices-icon-cross;
padding-left: $choices-button-offset*2;
border-left: 1px solid darken($choices-primary-color, 10%);
background-image: url($choices-button-icon-path + '/cross.svg');
background-size: $choices-button-dimension;
width: $choices-button-dimension;
line-height: 1;
opacity: 0.75;
border-radius: 0;
&:hover,
&:focus {
opacity: 1;
}
opacity: .75;
&:hover, &:focus { opacity: 1; }
}
}
@ -172,43 +127,26 @@ $choices-z-index: 1 !default;
font-size: $choices-font-size-md;
min-height: 44px;
overflow: hidden;
.is-focused &,
.is-open & {
border-color: color.adjust($choices-keyline-color, $lightness: -15%);
}
.is-open & {
border-radius: $choices-border-radius $choices-border-radius 0 0;
}
.is-flipped.is-open & {
border-radius: 0 0 $choices-border-radius $choices-border-radius;
}
.is-focused &, .is-open & { border-color: darken($choices-keyline-color, 15%); }
.is-open & { border-radius: $choices-border-radius $choices-border-radius 0 0; }
.is-flipped.is-open & { border-radius: 0 0 $choices-border-radius $choices-border-radius; }
}
.#{$choices-selector}__list {
margin: 0;
padding-left: 0;
list-style: none;
&[aria-expanded] {
@extend %choices-dropdown;
}
}
.#{$choices-selector}__list--single {
display: inline-block;
padding: 4px 16px 4px 4px;
width: 100%;
[dir="rtl"] & {
padding-right: 4px;
padding-left: 16px;
}
.#{$choices-selector}__item {
width: 100%;
}
.#{$choices-selector}__item { width: 100%; }
}
.#{$choices-selector}__list--multiple {
@ -223,35 +161,28 @@ $choices-z-index: 1 !default;
margin-right: 3.75px;
margin-bottom: 3.75px;
background-color: $choices-primary-color;
border: 1px solid color.adjust($choices-primary-color, $lightness: -5%);
color: #fff;
border: 1px solid darken($choices-primary-color, 5%);
color: #FFFFFF;
word-break: break-all;
box-sizing: border-box;
&[data-deletable] {
padding-right: 5px;
}
&[data-deletable] { padding-right: 5px; }
[dir="rtl"] & {
margin-right: 0;
margin-left: 3.75px;
}
&.is-highlighted {
background-color: color.adjust($choices-primary-color, $lightness: -5%);
border: 1px solid color.adjust($choices-primary-color, $lightness: -10%);
background-color: darken($choices-primary-color, 5%);
border: 1px solid darken($choices-primary-color, 10%);
}
.is-disabled & {
background-color: color.adjust($choices-disabled-color, $lightness: -25%);
border: 1px solid color.adjust($choices-disabled-color, $lightness: -35%);
background-color: darken($choices-disabled-color, 25%);
border: 1px solid darken($choices-disabled-color, 35%);
}
}
}
%choices-dropdown {
visibility: hidden;
z-index: $choices-z-index;
.#{$choices-selector}__list--dropdown {
display: none;
z-index: 1;
position: absolute;
width: 100%;
background-color: $choices-bg-color-dropdown;
@ -262,22 +193,14 @@ $choices-z-index: 1 !default;
border-bottom-right-radius: $choices-border-radius;
overflow: hidden;
word-break: break-all;
will-change: visibility;
&.is-active {
visibility: visible;
}
.is-open & {
border-color: color.adjust($choices-keyline-color, $lightness: -15%);
}
&.is-active { display: block; }
.is-open & { border-color: darken($choices-keyline-color, 15%); }
.is-flipped & {
top: auto;
bottom: 100%;
margin-top: 0;
margin-bottom: -1px;
border-radius: 0.25rem 0.25rem 0 0;
border-radius: .25rem .25rem 0 0;
}
.#{$choices-selector}__list {
position: relative;
@ -290,16 +213,12 @@ $choices-z-index: 1 !default;
position: relative;
padding: 10px;
font-size: $choices-font-size-md;
[dir="rtl"] & {
text-align: right;
}
[dir="rtl"] & { text-align: right; }
}
.#{$choices-selector}__item--selectable {
@media (min-width: 640px) {
padding-right: 100px;
&::after {
&:after {
content: attr(data-select-text);
font-size: $choices-font-size-sm;
opacity: 0;
@ -308,106 +227,68 @@ $choices-z-index: 1 !default;
top: 50%;
transform: translateY(-50%);
}
[dir="rtl"] & {
text-align: right;
padding-left: 100px;
padding-right: 10px;
&::after {
&:after {
right: auto;
left: 10px;
}
}
}
&.is-highlighted {
background-color: color.mix(#000, #fff, 5%);
&::after {
opacity: 0.5;
}
background-color: mix(#000000, #FFFFFF, 5%);
&:after { opacity: .5; }
}
}
}
.#{$choices-selector}__list--dropdown {
@extend %choices-dropdown;
}
.#{$choices-selector}__item {
cursor: default;
}
.#{$choices-selector}__item--selectable {
cursor: pointer;
}
.#{$choices-selector}__item { cursor: default; }
.#{$choices-selector}__item--selectable { cursor: pointer; }
.#{$choices-selector}__item--disabled {
cursor: not-allowed;
user-select: none;
opacity: 0.5;
opacity: .5;
}
.#{$choices-selector}__heading {
font-weight: 600;
font-size: $choices-font-size-sm;
padding: 10px;
border-bottom: 1px solid color.adjust($choices-keyline-color, $lightness: 10%);
color: color.adjust(#333, $lightness: 30%);
border-bottom: 1px solid lighten($choices-keyline-color, 10%);
color: lighten(#333, 30%);
}
.#{$choices-selector}__button {
text-indent: -9999px;
-webkit-appearance: none;
appearance: none;
border: 0;
background-color: transparent;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
&:focus {
outline: none;
}
&:focus { outline: none; }
}
.#{$choices-selector}__input {
display: inline-block;
vertical-align: baseline;
background-color: $choices-bg-color;
background-color: mix(#000000, #FFFFFF, 2.5%);
font-size: $choices-font-size-md;
margin-bottom: 5px;
border: 0;
border-radius: 0;
max-width: 100%;
padding: 4px 0 4px 2px;
&:focus {
outline: 0;
}
&::-webkit-search-decoration,
&::-webkit-search-cancel-button,
&::-webkit-search-results-button,
&::-webkit-search-results-decoration {
display: none;
}
&::-ms-clear,
&::-ms-reveal {
display: none;
width: 0;
height: 0;
}
&:focus { outline: 0; }
[dir="rtl"] & {
padding-right: 2px;
padding-left: 0;
}
}
.#{$choices-selector}__placeholder {
opacity: 0.5;
}
.#{$choices-selector}__placeholder { opacity: .5; }
/* ===== End of Choices ====== */
/*===== End of Choices ======*/

29
bower.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "choices.js",
"version": "2.7.1",
"description": "A vanilla JS customisable text input/select box plugin",
"main": [
"./assets/scripts/dist/choices.js",
"./assets/styles/css/choices.css"
],
"authors": [
"Josh Johnson"
],
"license": "MIT",
"homepage": "https://joshuajohnson.co.uk/Choices/",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"keywords": [
"customisable",
"input",
"select",
"vanilla",
"plugin",
"js"
]
}

View file

@ -1,50 +0,0 @@
/* eslint-disable no-param-reassign */
const { JSDOM } = require('jsdom');
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) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
function ignoreExtensions(extensions = [], returnValue = {}) {
function noop() {
return returnValue;
}
extensions.forEach(ext => {
require.extensions[ext] = noop;
});
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
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);
ignoreExtensions(['.scss', '.css']);
ignoreExtensions(['.jpg', '.png', '.svg'], '');

View file

@ -1,15 +0,0 @@
import { defineConfig } from 'cypress'
export default defineConfig({
video: false,
projectId: 'n7g5qp',
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://localhost:3001/test',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
},
})

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,431 +0,0 @@
describe('Choices - text element', () => {
beforeEach(() => {
cy.visit('/text', {
onBeforeLoad(win) {
cy.stub(win.console, 'warn').as('consoleWarn');
},
});
});
describe('scenarios', () => {
const textInput = 'testing';
describe('basic', () => {
describe('adding items', () => {
it('allows me to input items', () => {
cy.get('[data-test-hook=basic]')
.find('.choices__input--cloned')
.type(textInput)
.type('{enter}');
cy.get('[data-test-hook=basic]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($el) => {
expect($el).to.contain(textInput);
});
});
it('updates the value of the original input', () => {
cy.get('[data-test-hook=basic]')
.find('.choices__input--cloned')
.type(textInput)
.type('{enter}');
cy.get('[data-test-hook=basic]')
.find('.choices__input[hidden]')
.should('have.value', textInput);
});
describe('inputting data', () => {
it('shows a dropdown prompt', () => {
cy.get('[data-test-hook=basic]')
.find('.choices__input--cloned')
.type(textInput);
cy.get('[data-test-hook=basic]')
.find('.choices__list--dropdown')
.should('be.visible')
.should(($dropdown) => {
const dropdownText = $dropdown.text().trim();
expect(dropdownText).to.equal(
`Press Enter to add "${textInput}"`,
);
});
});
});
});
});
describe('editing items', () => {
beforeEach(() => {
for (let index = 0; index < 3; index++) {
cy.get('[data-test-hook=edit-items]')
.find('.choices__input--cloned')
.type(textInput)
.type('{enter}');
}
});
describe('on back space', () => {
it('allows me to change my entry', () => {
cy.get('[data-test-hook=edit-items]')
.find('.choices__input--cloned')
.type('{backspace}')
.type('-edited')
.type('{enter}');
cy.get('[data-test-hook=edit-items]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($choice) => {
expect($choice.data('value')).to.equal(`${textInput}-edited`);
});
});
});
describe('on cmd+a', () => {
beforeEach(() => {
cy.get('[data-test-hook=edit-items]')
.find('.choices__input--cloned')
.type('{cmd}a');
});
it('highlights all items', () => {
cy.get('[data-test-hook=edit-items]')
.find('.choices__list--multiple .choices__item')
.each(($choice) => {
expect($choice.hasClass('is-highlighted')).to.equal(true);
});
});
describe('on backspace', () => {
it('clears all inputted values', () => {
// two backspaces are needed as Cypress has an issue where
// it will also insert an 'a' character into the text input
cy.get('[data-test-hook=edit-items]')
.find('.choices__input--cloned')
.type('{backspace}{backspace}');
cy.get('[data-test-hook=edit-items]')
.find('.choices__list--multiple .choices__item')
.should('have.length', 0);
});
});
});
});
describe('remove button', () => {
beforeEach(() => {
cy.get('[data-test-hook=remove-button]')
.find('.choices__input--cloned')
.type(`${textInput}`)
.type('{enter}');
});
describe('on click', () => {
it('removes respective choice', () => {
cy.get('[data-test-hook=remove-button]')
.find('.choices__list--multiple')
.children()
.should(($items) => {
expect($items.length).to.equal(1);
});
cy.get('[data-test-hook=remove-button]')
.find('.choices__list--multiple .choices__item')
.last()
.find('.choices__button')
.focus()
.click();
cy.get('[data-test-hook=remove-button]')
.find('.choices__list--multiple .choices__item')
.should(($items) => {
expect($items.length).to.equal(0);
});
});
it('updates the value of the original input', () => {
cy.get('[data-test-hook=remove-button]')
.find('.choices__list--multiple .choices__item')
.last()
.find('.choices__button')
.focus()
.click();
cy.get('[data-test-hook=remove-button]')
.find('.choices__input[hidden]')
.then(($input) => {
expect($input.val()).to.not.contain(textInput);
});
});
});
});
describe('unique values only', () => {
describe('unique values', () => {
beforeEach(() => {
cy.get('[data-test-hook=unique-values]')
.find('.choices__input--cloned')
.type(`${textInput}`)
.type('{enter}')
.type(`${textInput}`)
.type('{enter}');
});
it('only allows me to input unique values', () => {
cy.get('[data-test-hook=unique-values]')
.find('.choices__list--multiple')
.first()
.children()
.should(($items) => {
expect($items.length).to.equal(1);
});
});
describe('inputting a non-unique value', () => {
it('displays dropdown prompt', () => {
cy.get('[data-test-hook=unique-values]')
.find('.choices__list--dropdown')
.should('be.visible')
.should(($dropdown) => {
const dropdownText = $dropdown.text().trim();
expect(dropdownText).to.equal(
'Only unique values can be added',
);
});
});
});
});
});
describe('input limit', () => {
const inputLimit = 5;
beforeEach(() => {
for (let index = 0; index < inputLimit + 1; index++) {
cy.get('[data-test-hook=input-limit]')
.find('.choices__input--cloned')
.type(`${textInput} + ${index}`)
.type('{enter}');
}
});
it('does not let me input more than 5 choices', () => {
cy.get('[data-test-hook=input-limit]')
.find('.choices__list--multiple')
.first()
.children()
.should(($items) => {
expect($items.length).to.equal(inputLimit);
});
});
describe('reaching input limit', () => {
it('displays dropdown prompt', () => {
cy.get('[data-test-hook=input-limit]')
.find('.choices__list--dropdown')
.should('be.visible')
.should(($dropdown) => {
const dropdownText = $dropdown.text().trim();
expect(dropdownText).to.equal(
`Only ${inputLimit} values can be added`,
);
});
});
});
});
describe('add item filter', () => {
describe('inputting a value that satisfies the filter', () => {
const input = 'joe@bloggs.com';
it('allows me to add choice', () => {
cy.get('[data-test-hook=add-item-filter]')
.find('.choices__input--cloned')
.type(input)
.type('{enter}');
cy.get('[data-test-hook=add-item-filter]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($choice) => {
expect($choice.text().trim()).to.equal(input);
});
});
});
describe('inputting a value that does not satisfy the regex', () => {
it('displays dropdown prompt', () => {
cy.get('[data-test-hook=add-item-filter]')
.find('.choices__input--cloned')
.type(`this is not an email address`)
.type('{enter}');
cy.get('[data-test-hook=add-item-filter]')
.find('.choices__list--dropdown')
.should('be.visible')
.should(($dropdown) => {
const dropdownText = $dropdown.text().trim();
expect(dropdownText).to.equal(
'Only values matching specific conditions can be added',
);
});
});
});
});
describe('prepend/append', () => {
beforeEach(() => {
cy.get('[data-test-hook=prepend-append]')
.find('.choices__input--cloned')
.type(textInput)
.type('{enter}');
});
it('prepends and appends value to inputted value', () => {
cy.get('[data-test-hook=prepend-append]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($choice) => {
expect($choice.data('value')).to.equal(`before-${textInput}-after`);
});
});
it('displays just the inputted value to the user', () => {
cy.get('[data-test-hook=prepend-append]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($choice) => {
expect($choice.text()).to.not.contain(`before-${textInput}-after`);
expect($choice.text()).to.contain(textInput);
});
});
});
describe('adding items disabled', () => {
it('does not allow me to input data', () => {
cy.get('[data-test-hook=adding-items-disabled]')
.find('.choices__input--cloned')
.should('be.disabled');
});
});
describe('disabled via attribute', () => {
it('does not allow me to input data', () => {
cy.get('[data-test-hook=disabled-via-attr]')
.find('.choices__input--cloned')
.should('be.disabled');
});
});
describe('pre-populated choices', () => {
it('pre-populates choices', () => {
cy.get('[data-test-hook=prepopulated]')
.find('.choices__list--multiple .choices__item')
.should(($choices) => {
expect($choices.length).to.equal(2);
});
cy.get('[data-test-hook=prepopulated]')
.find('.choices__list--multiple .choices__item')
.first()
.should(($choice) => {
expect($choice.text().trim()).to.equal('Josh Johnson');
});
cy.get('[data-test-hook=prepopulated]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($choice) => {
expect($choice.text().trim()).to.equal('Joe Bloggs');
});
});
});
describe('placeholder', () => {
/*
{
placeholder: true,
placeholderValue: 'I am a placeholder',
}
*/
describe('when no value has been inputted', () => {
it('displays a placeholder', () => {
cy.get('[data-test-hook=placeholder]')
.find('.choices__input--cloned')
.should('have.attr', 'placeholder', 'I am a placeholder');
});
});
});
describe('allow html', () => {
describe('is undefined', () => {
it('logs a deprecation warning', () => {
cy.get('@consoleWarn').should(
'be.calledOnceWithExactly',
'Deprecation warning: allowHTML will default to false in a future release. To render HTML in Choices, you will need to set it to true. Setting allowHTML will suppress this message.',
);
});
it('does not show html as text', () => {
cy.get('[data-test-hook=allowhtml-undefined]')
.find('.choices__list--multiple .choices__item')
.first()
.should(($choice) => {
expect($choice.text().trim()).to.equal('Mason Rogers');
});
});
});
describe('set to true', () => {
it('does not show html as text', () => {
cy.get('[data-test-hook=allowhtml-true]')
.find('.choices__list--multiple .choices__item')
.first()
.should(($choice) => {
expect($choice.text().trim()).to.equal('Mason Rogers');
});
});
});
describe('set to false', () => {
it('shows html as text', () => {
cy.get('[data-test-hook=allowhtml-false]')
.find('.choices__list--multiple .choices__item')
.first()
.should(($choice) => {
expect($choice.text().trim()).to.equal('<b>Mason Rogers</b>');
});
});
});
});
describe('within form', () => {
describe('inputting item', () => {
describe('on enter key', () => {
it('does not submit form', () => {
cy.get('[data-test-hook=within-form] form').then(($form) => {
$form.submit(() => {
// this will fail the test if the form submits
throw new Error('Form submitted');
});
});
cy.get('[data-test-hook=within-form]')
.find('.choices__input--cloned')
.type(textInput)
.type('{enter}');
cy.get('[data-test-hook=within-form]')
.find('.choices__list--multiple .choices__item')
.last()
.should(($el) => {
expect($el).to.contain(textInput);
});
});
});
});
});
});
});

View file

@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View file

@ -1,17 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};

View file

@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View file

@ -1,20 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

51
deploy.js Normal file
View file

@ -0,0 +1,51 @@
const fs = require('fs'),
path = require('path'),
config = {
files: ['bower.json', 'package.json', '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: index,
name: val.replace(/^--/, '')
}
return;
}
if(arg && ((arg.index+1 === index ))) {
args[arg.name] = val;
}
});
return args;
};
/**
* Loop through passed files updating version number
* @param {Object} config
*/
const updateVersion = (config) => {
const args = argvToObject();
const currentVersion = args.current;
const newVersion = args.new;
config.files.forEach((file) => {
const filePath = path.join(__dirname, file);
const regex = new RegExp(currentVersion, 'g');
let contents = fs.readFileSync(filePath, 'utf-8');
contents = contents.replace(regex, newVersion);
fs.writeFileSync(filePath, contents);
});
console.log(`Updated version to ${newVersion}`);
};
updateVersion(config);

466
index.html Normal file
View file

@ -0,0 +1,466 @@
<!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">
<!-- Ignore these -->
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.7.1">
<!-- End ignore these -->
<!-- Optional includes -->
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=es5,fetch,Element.prototype.classList,requestAnimationFrame,Node.insertBefore,Node.firstChild,Object.assign"></script>
<!-- End optional includes -->
<!-- Choices includes -->
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.7.1">
<script src="assets/scripts/dist/choices.min.js?version=2.7.1"></script>
<!-- End Choices includes -->
<!--[if lt IE 9]>
<style>
.hidden-ie { display: none; }
.visible-ie { display: block; }
</style>
<![endif]-->
</head>
<body>
<div class="container">
<div class="section">
<a href="https://github.com/jshjohnson/Choices" class="logo">
<img src="assets/images/logo.svg" alt="Choices.js logo" class="logo__img hidden-ie">
<h1 class="visible-ie">Choices.js</h1>
</a>
<p>Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.</p>
<p>For all config options, visit the <a href="https://github.com/jshjohnson/Choices">GitHub repo</a>.</p>
<hr>
<h2>Text inputs</h2>
<label for="choices-text-remove-button">Limited to 5 values with remove button</label>
<input class="form-control" id="choices-text-remove-button" type="text" value="preset-1,preset-2" placeholder="Enter something">
<label for="choices-text-unique-values">Unique values only, no pasting</label>
<input class="form-control" id="choices-text-unique-values" type="text" value="preset-1, preset-2" placeholder="This is a placeholder" class="custom class">
<label for="choices-text-email-filter">Email addresses only</label>
<input class="form-control" id="choices-text-email-filter" type="text" placeholder="This is a placeholder">
<label for="choices-text-disabled">Disabled</label>
<input class="form-control" id="choices-text-disabled" type="text" value="josh@joshuajohnson.co.uk, joe@bloggs.co.uk" placeholder="This is a placeholder">
<label for="choices-text-prepend-append-value">Prepends and appends a value to each items return value</label>
<input class="form-control" id="choices-text-prepend-append-value" type="text" value="preset-1, preset-2" placeholder="This is a placeholder">
<label for="choices-text-preset-values">Preset values passed through options</label>
<input class="form-control" id="choices-text-preset-values" type="text" value="olivia@benson.com" placeholder="This is a placeholder">
<label for="choices-text-i18n">I18N labels</label>
<input class="form-control" data-trigger id="choices-text-i18n" type="text">
<label for="choices-text-rtl">Right-to-left</label>
<input class="form-control" data-trigger id="choices-text-rtl" type="text" value="Value 1, Value 2" dir="rtl">
<hr>
<h2>Multiple select input</h2>
<label for="choices-multiple-default">Default</label>
<select class="form-control" data-trigger name="choices-multiple-default" id="choices-multiple-default" placeholder="This is a placeholder" multiple>
<option value="Dropdown item 1" selected>Dropdown item 1</option>
<option value="Dropdown item 2">Dropdown item 2</option>
<option value="Dropdown item 3">Dropdown item 3</option>
<option value="Dropdown item 4" disabled>Dropdown item 4</option>
</select>
<label for="choices-multiple-remove-button">With remove button</label>
<select class="form-control" name="choices-multiple-remove-button" id="choices-multiple-remove-button" placeholder="This is a placeholder" multiple>
<option value="Dropdown item 1" selected>Dropdown item 1</option>
<option value="Dropdown item 2">Dropdown item 2</option>
<option value="Dropdown item 3">Dropdown item 3</option>
<option value="Dropdown item 4">Dropdown item 4</option>
</select>
<label for="choices-multiple-groups">Option groups</label>
<select class="form-control" name="choices-multiple-groups" id="choices-multiple-groups" placeholder="This is a placeholder" multiple>
<optgroup label="UK">
<option value="London">London</option>
<option value="Manchester">Manchester</option>
<option value="Liverpool">Liverpool</option>
</optgroup>
<optgroup label="FR">
<option value="Paris">Paris</option>
<option value="Lyon">Lyon</option>
<option value="Marseille">Marseille</option>
</optgroup>
<optgroup label="DE" disabled>
<option value="Hamburg">Hamburg</option>
<option value="Munich">Munich</option>
<option value="Berlin">Berlin</option>
</optgroup>
<optgroup label="US">
<option value="New York">New York</option>
<option value="Washington" disabled>Washington</option>
<option value="Michigan">Michigan</option>
</optgroup>
<optgroup label="SP">
<option value="Madrid">Madrid</option>
<option value="Barcelona">Barcelona</option>
<option value="Malaga">Malaga</option>
</optgroup>
<optgroup label="CA">
<option value="Montreal">Montreal</option>
<option value="Toronto">Toronto</option>
<option value="Vancouver">Vancouver</option>
</optgroup>
</select>
<p><small>If the following example do not load, the Discogs rate limit has probably been reached. Try again later!</small></p>
<label for="choices-multiple-remote-fetch">Options from remote source (Fetch API) &amp; limited to 5</label>
<select class="form-control" name="choices-multiple-remote-fetch" id="choices-multiple-remote-fetch" multiple></select>
<label for="choices-multiple-rtl">Right-to-left</label>
<select class="form-control" data-trigger name="choices-multiple-rtl" id="choices-multiple-rtl" placeholder="This is a placeholder" multiple dir="rtl">
<option value="Dropdown item 1" selected>Dropdown item 1</option>
<option value="Dropdown item 2">Dropdown item 2</option>
<option value="Dropdown item 3">Dropdown item 3</option>
<option value="Dropdown item 4" disabled>Dropdown item 4</option>
</select>
<hr>
<h2>Single select input</h2>
<label for="choices-single-default">Default</label>
<select class="form-control" data-trigger name="choices-single-default" id="choices-single-default" placeholder="This is a search placeholder">
<option selected disabled>This is a placeholder</option>
<option value="Dropdown item 1">Dropdown item 1</option>
<option value="Dropdown item 2">Dropdown item 2</option>
<option value="Dropdown item 3">Dropdown item 3</option>
</select>
<p><small>If the following two examples do not load, the Discogs rate limit has probably been reached. Try again later!</small></p>
<label for="choices-single-remote-fetch">Options from remote source (Fetch API)</label>
<select class="form-control" name="choices-single-remote-fetch" id="choices-single-remote-fetch" placeholder="Pick an Arctic Monkeys record"></select>
<label for="choices-single-remove-xhr">Options from remote source (XHR) &amp; remove button</label>
<select class="form-control" name="choices-single-remove-xhr" id="choices-single-remove-xhr" placeholder="Pick a Smiths record"></select>
<label for="choices-single-groups">Option groups</label>
<select class="form-control" data-trigger name="choices-single-groups" id="choices-single-groups" placeholder="This is a placeholder">
<optgroup label="UK">
<option value="London">London</option>
<option value="Manchester">Manchester</option>
<option value="Liverpool">Liverpool</option>
</optgroup>
<optgroup label="FR">
<option value="Paris">Paris</option>
<option value="Lyon">Lyon</option>
<option value="Marseille">Marseille</option>
</optgroup>
<optgroup label="DE" disabled>
<option value="Hamburg">Hamburg</option>
<option value="Munich">Munich</option>
<option value="Berlin">Berlin</option>
</optgroup>
<optgroup label="US">
<option value="New York">New York</option>
<option value="Washington" disabled>Washington</option>
<option value="Michigan">Michigan</option>
</optgroup>
<optgroup label="SP">
<option value="Madrid">Madrid</option>
<option value="Barcelona">Barcelona</option>
<option value="Malaga">Malaga</option>
</optgroup>
<optgroup label="CA">
<option value="Montreal">Montreal</option>
<option value="Toronto">Toronto</option>
<option value="Vancouver">Vancouver</option>
</optgroup>
</select>
<label for="choices-single-rtl">Right-to-left</label>
<select class="form-control" data-trigger name="choices-single-rtl" id="choices-single-rtl" placeholder="This is a placeholder" dir="rtl">
<option value="Dropdown item 1">Dropdown item 1</option>
<option value="Dropdown item 2">Dropdown item 2</option>
<option value="Dropdown item 3">Dropdown item 3</option>
</select>
<label for="choices-single-no-search">Options added via config with no search</label>
<select class="form-control" name="choices-single-no-search" id="choices-single-no-search">
<option value="0">Zero</option>
</select>
<label for="choices-single-preset-options">Option and option groups added via config</label>
<select class="form-control" name="choices-single-preset-options" id="choices-single-preset-options" placeholder="This is a placeholder"></select>
<label for="choices-single-selected-option">Option selected via config</label>
<select class="form-control" name="choices-single-selected-option" id="choices-single-selected-option" placeholder="This is a placeholder"></select>
<label for="choices-single-no-sorting">Options without sorting</label>
<select class="form-control" name="choices-single-no-sorting" id="choices-single-no-sorting" placeholder="This is a placeholder">
<option value="Madrid">Madrid</option>
<option value="Toronto">Toronto</option>
<option value="Vancouver">Vancouver</option>
<option value="London">London</option>
<option value="Manchester">Manchester</option>
<option value="Liverpool">Liverpool</option>
<option value="Paris">Paris</option>
<option value="Malaga">Malaga</option>
<option value="Washington" disabled>Washington</option>
<option value="Lyon">Lyon</option>
<option value="Marseille">Marseille</option>
<option value="Hamburg">Hamburg</option>
<option value="Munich">Munich</option>
<option value="Barcelona">Barcelona</option>
<option value="Berlin">Berlin</option>
<option value="Montreal">Montreal</option>
<option value="New York">New York</option>
<option value="Michigan">Michigan</option>
</select>
<label for="choices-custom-templates">Custom templates</label>
<select class="form-control" name="choices-custom-templates" id="choices-custom-templates" placeholder="This is a placeholder">
<option value="React">React</option>
<option value="React">Angular</option>
<option value="React">Ember</option>
<option value="React">Vue</option>
</select>
<p>Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if the value of 'States' is 'New York'</p>
<label for="states">States</label>
<select class="form-control" name="states" id="states" placeholder="Choose a state">
<option value="Michigan">Michigan</option>
<option value="Texas">Texas</option>
<option value="Chicago">Chicago</option>
<option value="New York">New York</option>
<option value="Washington">Washington</option>
</select>
<label for="boroughs">Boroughs</label>
<select class="form-control" name="boroughs" id="boroughs" placeholder="Choose a borough">
<option value="The Bronx">The Bronx</option>
<option value="Brooklyn">Brooklyn</option>
<option value="Manhattan">Manhattan</option>
<option value="Queens">Queens</option>
<option value="Staten Island">Staten Island</option>
</select>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var textRemove = new Choices(document.getElementById('choices-text-remove-button'), {
delimiter: ',',
editItems: true,
maxItemCount: 5,
removeItemButton: true,
});
var textUniqueVals = new Choices('#choices-text-unique-values', {
paste: false,
duplicateItems: false,
editItems: true,
});
var texti18n = new Choices('#choices-text-i18n', {
paste: false,
duplicateItems: false,
editItems: true,
addItemText: (value) => {
return `Appuyez sur Entrée pour ajouter <b>"${value}"</b>`;
},
maxItemText: (maxItemCount) => {
return `${maxItemCount} valeurs peuvent être ajoutées`;
},
uniqueItemText: 'Cette valeur est déjà présente',
});
var textEmailFilter = new Choices('#choices-text-email-filter', {
editItems: true,
regexFilter: /^(([^<>()\[\]\\.,;:\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,}))$/,
});
var textDisabled = new Choices('#choices-text-disabled', {
addItems: false,
removeItems: false,
}).disable();
var textPrependAppendVal = new Choices('#choices-text-prepend-append-value', {
prependValue: 'item-',
appendValue: '-' + Date.now(),
}).removeActiveItems();
var textPresetVal = new Choices('#choices-text-preset-values', {
items: ['josh@joshuajohnson.co.uk', { value: 'joe@bloggs.co.uk', label: 'Joe Bloggs' } ],
});
var multipleDefault = new Choices(document.getElementById('choices-multiple-groups'));
var multipleFetch = new Choices('#choices-multiple-remote-fetch', {
placeholder: true,
placeholderValue: 'Pick an Strokes record',
maxItemCount: 5,
}).ajax(function(callback) {
fetch('https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
.then(function(response) {
response.json().then(function(data) {
callback(data.releases, 'title', 'title');
});
})
.catch(function(error) {
console.error(error);
});
});
var multipleCancelButton = new Choices('#choices-multiple-remove-button', {
removeItemButton: true,
})
var singleFetch = new Choices('#choices-single-remote-fetch', {
placeholder: true,
placeholderValue: 'Pick an Arctic Monkeys record'
}).ajax(function(callback) {
fetch('https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
.then(function(response) {
response.json().then(function(data) {
callback(data.releases, 'title', 'title');
singleFetch.setValueByChoice('Fake Tales Of San Francisco');
});
})
.catch(function(error) {
console.error(error);
});
});
var singleXhrRemove = new Choices('#choices-single-remove-xhr', {
removeItemButton: true,
}).ajax(function(callback) {
var request = new XMLHttpRequest();
request.open('get', 'https://api.discogs.com/artists/83080/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW', true);
request.onreadystatechange = function() {
var status;
var data;
if (request.readyState === 4) {
status = request.status;
if (status === 200) {
data = JSON.parse(request.responseText);
callback(data.releases, 'title', 'title');
singleXhrRemove.setValueByChoice('How Soon Is Now?');
} else {
console.error(status);
}
}
}
request.send();
});
var genericExamples = new Choices('[data-trigger]', {
placeholderValue: 'This is a placeholder set in the config'
});
var singleNoSearch = new Choices('#choices-single-no-search', {
search: false,
removeItemButton: true,
choices: [
{value: 'One', label: 'Label One'},
{value: 'Two', label: 'Label Two', disabled: true},
{value: 'Three', label: 'Label Three'},
],
}).setChoices([
{value: 'Four', label: 'Label Four', disabled: true},
{value: 'Five', label: 'Label Five'},
{value: 'Six', label: 'Label Six', selected: true},
], 'value', 'label', false);
var singlePresetOpts = new Choices('#choices-single-preset-options', {
placeholder: true,
}).setChoices([{
label: 'Group one',
id: 1,
disabled: false,
choices: [
{value: 'Child One', label: 'Child One', selected: true},
{value: 'Child Two', label: 'Child Two', disabled: true},
{value: 'Child Three', label: 'Child Three'},
]
},
{
label: 'Group two',
id: 2,
disabled: false,
choices: [
{value: 'Child Four', label: 'Child Four', disabled: true},
{value: 'Child Five', label: 'Child Five'},
{value: 'Child Six', label: 'Child Six'},
]
}], 'value', 'label');
var singleSelectedOpt = new Choices('#choices-single-selected-option', {
choices: [
{value: 'One', label: 'Label One', selected: true},
{value: 'Two', label: 'Label Two', disabled: true},
{value: 'Three', label: 'Label Three'},
],
}).setValueByChoice('Two');
var singleNoSorting = new Choices('#choices-single-no-sorting', {
shouldSort: false,
});
var states = new Choices(document.getElementById('states'));
states.passedElement.addEventListener('change', function(e) {
if(e.detail.value === 'New York') {
boroughs.enable();
} else {
boroughs.disable();
}
});
var customTemplates = new Choices(document.getElementById('choices-custom-templates'), {
callbackOnCreateTemplates: function (strToEl) {
var classNames = this.config.classNames;
return {
item: (data) => {
return strToEl(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''}>
<span style="margin-right:10px;">🎉</span> ${data.label}
</div>
`);
},
choice: (data) => {
return strToEl(`
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="${this.config.itemSelectText}" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}" ${data.groupId > 0 ? 'role="treeitem"' : 'role="option"'}>
<span style="margin-right:10px;">👉🏽</span> ${data.label}
</div>
`);
},
};
}
});
var boroughs = new Choices(document.getElementById('boroughs')).disable();
});
</script>
<!-- Google Analytics - Ignore me -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-31575166-1', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->
</body>
</html>

View file

@ -1,17 +0,0 @@
{
"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,
"strictNullChecks": false
}
}

View file

@ -1,14 +0,0 @@
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',
};

23844
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,33 +1,20 @@
{
"name": "choices.js",
"version": "10.2.0",
"version": "2.7.1",
"description": "A vanilla JS customisable text input/select box plugin",
"main": "./public/assets/scripts/choices.js",
"types": "./public/types/src/index.d.ts",
"main": "./assets/scripts/dist/choices.min.js",
"scripts": {
"start": "run-p js:watch css:watch",
"build": "run-p js:build css:build",
"lint": "run-p lint:js lint:scss",
"lint:js": "eslint src/scripts/**/*.ts",
"lint:scss": "stylelint src/**/*.scss",
"bundlesize": "bundlesize",
"cypress:run": "cypress run --browser chrome",
"cypress:open": "cypress open",
"cypress:ci": "cypress run --browser chrome --record --group $GITHUB_REF --ci-build-id $GITHUB_SHA",
"test": "run-s test:unit test:e2e",
"test:unit": "cross-env TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha",
"test:unit:watch": "npm run test:unit -- --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": "cross-env NODE_ENV=development node server.js",
"js:build": "webpack --config webpack.config.prod.js",
"start": "node server.js",
"css:watch": "nodemon -e scss -x \"npm run css:build\"",
"css:build": "run-s css:sass css:prefix css:min",
"css:sass": "sass -I scss src/styles/base.scss public/assets/styles/base.css && sass -I scss src/styles/choices.scss public/assets/styles/choices.css",
"css:prefix": "postcss public/assets/styles/*.css --use autoprefixer --no-map --env prod --dir public/assets/styles",
"css:min": "csso public/assets/styles/base.css --output public/assets/styles/base.min.css && csso public/assets/styles/choices.css --output public/assets/styles/choices.min.css",
"deploy": "git subtree push --prefix public origin gh-pages",
"prepublishOnly": "npm run build"
"css:build": "npm run css:sass -s && npm run css:prefix -s && npm run css:min -s",
"css:sass": "node-sass --output-style expanded --include-path scss assets/styles/scss/base.scss assets/styles/css/base.css && node-sass --output-style expanded --include-path scss assets/styles/scss/choices.scss assets/styles/css/choices.css",
"css:prefix": "postcss --use autoprefixer -b 'last 2 versions' assets/styles/css/*.css -d assets/styles/css/",
"css:min": "csso assets/styles/css/base.css assets/styles/css/base.min.css && csso assets/styles/css/choices.css assets/styles/css/choices.min.css",
"js:build": "concurrently --prefix-colors yellow,green \"webpack --minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"",
"js:test": "./node_modules/karma/bin/karma start --single-run --no-auto-watch tests/karma.config.js",
"js:test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js",
"version": "node version.js --current $npm_package_version --new $npm_config_newVersion",
"postversion": "npm run js:build"
},
"repository": {
"type": "git",
@ -35,110 +22,57 @@
},
"author": "Josh Johnson",
"license": "MIT",
"files": [
"public/assets/scripts",
"public/assets/styles",
"public/types",
"src",
"!src/**/*.test.js",
"types"
],
"bugs": {
"url": "https://github.com/jshjohnson/Choices/issues"
},
"homepage": "https://github.com/jshjohnson/Choices#readme",
"keywords": [
"customisable",
"input",
"select",
"vanilla",
"plugin",
"js"
],
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/preset-env": "^7.20.2",
"@babel/register": "^7.18.9",
"@types/chai": "^4.3.4",
"@types/mocha": "^10.0.1",
"@types/sinon": "^10.0.13",
"@types/sinon-chai": "^3.2.9",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.13",
"babel-loader": "^9.1.0",
"bundlesize": "^0.18.1",
"chai": "^4.3.7",
"cross-env": "^7.0.3",
"csso-cli": "^4.0.1",
"cypress": "11.2.0",
"eslint": "^8.28.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-sort-class-members": "^1.15.2",
"eslint-webpack-plugin": "^3.2.0",
"express": "^4.18.2",
"husky": "^8.0.2",
"jsdom": "^20.0.3",
"lint-staged": "^13.0.4",
"mocha": "^10.1.0",
"nodemon": "^2.0.20",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"postcss": "^8.4.19",
"postcss-cli": "^10.0.0",
"prettier": "^2.8.0",
"sass": "^1.56.1",
"sinon": "^15.0.0",
"sinon-chai": "^3.7.0",
"stylelint": "^14.15.0",
"stylelint-config-standard": "^29.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"ts-loader": "^9.4.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.3",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"webpack-dev-middleware": "^6.0.1",
"webpack-hot-middleware": "^2.25.3"
"autoprefixer": "^6.3.3",
"babel-core": "^6.7.2",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"concurrently": "^3.1.0",
"csso": "^1.8.2",
"es6-promise": "^3.2.1",
"eslint": "^3.3.0",
"eslint-config-airbnb": "^10.0.1",
"eslint-loader": "^1.5.0",
"eslint-plugin-import": "^1.13.0",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1",
"jasmine-core": "2.4.1",
"karma": "^1.1.0",
"karma-coverage": "^1.0.0",
"karma-es6-shim": "^1.0.0",
"karma-htmlfile-reporter": "^0.3.4",
"karma-jasmine": "^1.0.2",
"karma-phantomjs-launcher": "^1.0.1",
"karma-spec-reporter": "0.0.26",
"karma-webpack": "^1.7.0",
"node-sass": "^3.4.2",
"nodemon": "^1.9.1",
"opn-cli": "^3.1.0",
"postcss-cli": "^2.5.1",
"webpack": "^1.12.14",
"webpack-dashboard": "^0.1.8",
"webpack-dev-server": "^1.14.1",
"whatwg-fetch": "^1.0.0",
"wrapper-webpack-plugin": "^0.1.7"
},
"dependencies": {
"deepmerge": "^4.2.2",
"fuse.js": "^6.6.2",
"redux": "^4.2.0"
"redux": "^3.3.1",
"fuse.js": "^2.2.2"
},
"npmName": "choices.js",
"npmFileMap": [
{
"basePath": "assets",
"files": [
"public/assets/scripts/*",
"public/assets/styles/*",
"public/types/*",
"src/icons/*"
"scripts/dist/*",
"styles/css/*",
"icons/*"
]
}
],
"nyc": {
"include": [
"src/scripts/**/**/*.js"
],
"exclude": [
"src/scripts/**/**/*.test.js"
]
},
"bundlesize": [
{
"path": "public/assets/scripts/choices.min.js",
"maxSize": "25 kB"
},
{
"path": "public/assets/styles/choices.min.css",
"maxSize": "2.5 kB"
}
]
}

View file

@ -1,27 +0,0 @@
// 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',
'Promise', // Promise is gate checked
);
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 it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
/*! choices.js v10.2.0 | © 2022 Josh Johnson | https://github.com/jshjohnson/Choices#readme */

View file

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["../../../src/styles/base.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAYA;EACE;EACA;;;AAGF;AAAA;AAAA;EAGE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;EAGE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAtFiB;;;AAyFnB;AAAA;EAEE,WA1FoB;;;AA6FtB;AAAA;EAEE,WA9FoB;;;AAiGtB;AAAA;EAEE,WAlGoB;;;AAqGtB;AAAA;EAEE,WAtGoB;;;AAyGtB;AAAA;EAEE,WA1GoB;;;AA6GtB;AAAA;EAEE,WA9GoB;;;AAiHtB;EACE;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAIJ;EACE;EACA,SAxIiB;EAyIjB;;AAEA;AAAA;AAAA;EAGE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE,eArKiB;;;AAwKnB;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,eArLiB;;;AAwLnB","file":"base.css"}

Some files were not shown because too many files have changed in this diff Show more