├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── bundle
├── easydropdown.js
└── easydropdown.js.map
├── config
├── mocha
│ └── mocha.opts
├── nyc
│ └── .nycrc.json
├── stylelint
│ └── .stylelintrc.json
├── tslint
│ └── tslint.json
└── webpack
│ ├── Constants
│ └── Environment.ts
│ ├── Rules
│ └── typescriptRule.ts
│ └── config.ts
├── demos
├── 01-basic-list.html
├── 02-basic-list-with-placeholder.html
├── 03-groups.html
├── 04-mixed-groups.html
├── 05-disabled-select.html
├── 06-disabled-group.html
├── 07-disabled-options.html
├── 08-preselected-value.html
├── 09-form-reset.html
├── 10-form-validation.html
├── 11-show-placeholder-when-open.html
├── 12-collision-detection.html
├── 13-live-updates.html
├── 14-loop.html
├── 15-callbacks.html
├── 16-programmatic-validation.html
├── easydropdown.js
├── easydropdown.js.map
├── index.html
├── scripts
│ ├── optionAdder.js
│ ├── submitHandler.js
│ └── themeSwitcher.js
├── style.css
└── themes
│ ├── README.md
│ ├── beanstalk.css
│ ├── flax.css
│ ├── ivy.css
│ └── theme.css.d.ts
├── docs
└── easydropdown-anatomy.png
├── package-lock.json
├── package.json
├── src
├── Components
│ ├── arrow.ts
│ ├── body.ts
│ ├── group.ts
│ ├── head.ts
│ ├── option.ts
│ ├── root.ts
│ └── value.ts
├── Config
│ ├── Behavior.ts
│ ├── Callbacks.ts
│ ├── ClassNames.ts
│ ├── Config.ts
│ └── Interfaces
│ │ ├── IBehavior.ts
│ │ ├── ICallback.ts
│ │ ├── ICallbacks.ts
│ │ ├── IClassNames.ts
│ │ ├── IConfig.ts
│ │ └── ISelectCallback.ts
├── Easydropdown
│ ├── Easydropdown.test.ts
│ ├── Easydropdown.ts
│ ├── EasydropdownFacade.test.ts
│ ├── EasydropdownFacade.ts
│ ├── Interfaces
│ │ └── IFactory.ts
│ ├── Timers.ts
│ ├── cache.ts
│ ├── factory.test.ts
│ └── factory.ts
├── Events
│ ├── Constants
│ │ ├── KeyCodes.ts
│ │ └── Selectors.ts
│ ├── EventBinding.ts
│ ├── Handlers
│ │ ├── handleBodyClick.test.ts
│ │ ├── handleBodyClick.ts
│ │ ├── handleBodyMousedown.test.ts
│ │ ├── handleBodyMousedown.ts
│ │ ├── handleBodyMouseover.test.ts
│ │ ├── handleBodyMouseover.ts
│ │ ├── handleHeadClick.test.ts
│ │ ├── handleHeadClick.ts
│ │ ├── handleItemsListScroll.test.ts
│ │ ├── handleItemsListScroll.ts
│ │ ├── handleSelectBlur.test.ts
│ │ ├── handleSelectBlur.ts
│ │ ├── handleSelectFocus.test.ts
│ │ ├── handleSelectFocus.ts
│ │ ├── handleSelectInvalid.test.ts
│ │ ├── handleSelectInvalid.ts
│ │ ├── handleSelectKeydown.test.ts
│ │ ├── handleSelectKeydown.ts
│ │ ├── handleSelectKeydownDown.test.ts
│ │ ├── handleSelectKeydownDown.ts
│ │ ├── handleSelectKeydownUp.test.ts
│ │ ├── handleSelectKeydownUp.ts
│ │ ├── handleSelectKeypress.test.ts
│ │ ├── handleSelectKeypress.ts
│ │ ├── handleWindowClick.test.ts
│ │ ├── handleWindowClick.ts
│ │ ├── handleWindowResize.test.ts
│ │ └── handleWindowResize.ts
│ ├── Interfaces
│ │ ├── IEventBinding.ts
│ │ ├── IEventHandler.ts
│ │ └── IHandlerParams.ts
│ ├── Mock
│ │ ├── createMockEvent.ts
│ │ ├── createMockGroups.ts
│ │ └── createMockHandlerParams.ts
│ ├── bindEvents.test.ts
│ ├── bindEvents.ts
│ └── getEventsList.ts
├── Renderer
│ ├── Constants
│ │ ├── AttributeChangeType.ts
│ │ └── DomChangeType.ts
│ ├── Dom.ts
│ ├── Interfaces
│ │ ├── IAttributeChange.ts
│ │ └── IPatchCommand.ts
│ ├── PatchCommand.ts
│ ├── Renderer.test.ts
│ ├── Renderer.ts
│ ├── dom.test.ts
│ ├── domDiff.test.ts
│ ├── domDiff.ts
│ ├── domPatch.test.ts
│ └── domPatch.ts
├── Shared
│ ├── Polyfills
│ │ └── Element.matches.ts
│ └── Util
│ │ ├── Constants
│ │ └── CollisionType.ts
│ │ ├── Interfaces
│ │ ├── ICollisionData.ts
│ │ └── IDispatchOpen.ts
│ │ ├── closestParent.test.ts
│ │ ├── closestParent.ts
│ │ ├── composeClassName.test.ts
│ │ ├── composeClassName.ts
│ │ ├── createDomElementFromHtml.ts
│ │ ├── detectBodyCollision.test.ts
│ │ ├── detectBodyCollision.ts
│ │ ├── dispatchOpen.test.ts
│ │ ├── dispatchOpen.ts
│ │ ├── getIsMobilePlatform.test.ts
│ │ ├── getIsMobilePlatform.ts
│ │ ├── killSelectReaction.ts
│ │ ├── pollForSelectChange.ts
│ │ ├── pollForSelectMutation.test.ts
│ │ ├── pollForSelectMutation.ts
│ │ ├── throttle.test.ts
│ │ └── throttle.ts
├── State
│ ├── Constants
│ │ ├── BodyStatus.ts
│ │ └── ScrollStatus.ts
│ ├── Group.ts
│ ├── InjectedActions
│ │ ├── closeOthers.test.ts
│ │ ├── closeOthers.ts
│ │ ├── scrollToView.test.ts
│ │ └── scrollToView.ts
│ ├── Interfaces
│ │ ├── IActions.ts
│ │ ├── IOnAction.ts
│ │ └── IPropertyDescriptor.ts
│ ├── Option.ts
│ ├── State.test.ts
│ ├── State.ts
│ ├── StateManager.ts
│ ├── StateMapper.test.ts
│ ├── StateMapper.ts
│ ├── resolveActions.test.ts
│ └── resolveActions.ts
├── index.ts
└── umd.ts
└── tsconfig.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ts linguist-language=JavaScript
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .tmp
2 | .DS_Store
3 | npm-debug.*
4 | node_modules
5 | dist
6 | bundle/dist
7 | bundle/easydropdown.dev.js
8 | bundle/easydropdown.dev.js.map
9 | coverage
10 | .nyc_output
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .nyc_output
2 | .vscode
3 | /config
4 | coverage
5 | demos
6 | docs
7 | bundle/dist
8 | bundle/*.dev.js
9 | *.map
10 | src
11 |
12 | .travis.yml
13 | tsconfig.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | after_success:
5 | - npm run coveralls
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "tslint.configFile": "./config/tslint/tslint.json",
3 | "css.validate": false,
4 | "stylelint.config": {
5 | "extends": "./config/stylelint/.stylelintrc.json"
6 | }
7 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | ## 4.2.0
5 | - Fixes issue where native select UI would be shown on iOS, regardless of whether `behavior.useNativeUiOnMobile` was set to `false`.
6 | - Adds a `.validate()` method to programmatically validate an instance, and a new demo (#16).
7 | - Adds a new callback `onOptionClick`.
8 |
9 | ## 4.1.1
10 | - Fixes issue introduced in 4.1.0 where UI no longer closed on select by default
11 | - Fixes issue introduced in 4.1.0 where clicking an option while `behavior.openOnFocus` set would close the UI without selecting any option.
12 |
13 | ## 4.1.0
14 | - Fixes a styling issue in Beanstalk and Ivy themes where native select element was discoverable on click to the left of the head element.
15 | - Fixes the `behavior.closeOnSelect` configuration option which not previously implemented internally.
16 | - Adds ensures that when `behavior.openOnFocus` is set, that selects also close on `blur`.
17 | - Updates and locks dependencies.
18 |
19 | ## 4.0.5
20 |
21 | - Fixes a styling issue with Firefox which caused a long scrolling page to scroll to the top when a dropdown is focused.
22 |
23 | ## 4.0.4
24 |
25 | - Fixes a styling issue with Firefox which caused the `head` element to jump when focused.
--------------------------------------------------------------------------------
/config/mocha/mocha.opts:
--------------------------------------------------------------------------------
1 | --require ts-node/register
2 | --recursive
3 | --watch-extensions ts
4 | **/*.test.ts
--------------------------------------------------------------------------------
/config/nyc/.nycrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": [
3 | "ts-node/register",
4 | "source-map-support/register"
5 | ],
6 | "reporter": [
7 | "lcov",
8 | "text"
9 | ],
10 | "extension": [
11 | ".ts"
12 | ],
13 | "exclude": [
14 | "demos",
15 | "config",
16 | "**/umd.ts",
17 | "**/index.ts",
18 | "**/*.d.ts",
19 | "**/**.test.ts",
20 | "**/Interfaces",
21 | "**/Constants",
22 | "**/Polyfills",
23 | "**/Mock",
24 | "bundle",
25 | "dist",
26 | "coverage"
27 | ],
28 | "statements": 100,
29 | "branches": 100,
30 | "functions": 100,
31 | "lines": 100,
32 | "all": true
33 | }
--------------------------------------------------------------------------------
/config/stylelint/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard"
4 | ],
5 | "rules": {
6 | "block-no-empty": true,
7 | "indentation": 4,
8 | "no-missing-end-of-source-newline": null
9 | }
10 | }
--------------------------------------------------------------------------------
/config/tslint/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended",
5 | "tslint-eslint-rules"
6 | ],
7 | "rules": {
8 | "arrow-parens": false,
9 | "curly": false,
10 | "eofline": false,
11 | "forin": false,
12 | "import-spacing": false,
13 | "ordered-imports": [true, {
14 | "grouped-imports": true
15 | }],
16 | "member-ordering": [true, {
17 | "order": [
18 | "public-instance-field",
19 | "protected-instance-field",
20 | "private-instance-field",
21 | "public-static-field",
22 | "protected-static-field",
23 | "private-static-field",
24 | "constructor",
25 | "public-instance-method",
26 | "protected-instance-method",
27 | "private-instance-method",
28 | "public-static-method",
29 | "protected-static-method",
30 | "private-static-method"
31 | ]
32 | }],
33 | "member-access": [true, "check-accessor"],
34 | "newline-before-return": true,
35 | "no-conditional-assignment": false,
36 | "no-console": [true, "log"],
37 | "object-curly-spacing": [true, "never"],
38 | "object-literal-sort-keys": false,
39 | "quotemark": [true, "single", "jsx-double"],
40 | "radix": false,
41 | "trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
42 | "typedef": [true, "call-signature"],
43 | "typedef-whitespace": false
44 | },
45 | "rulesDirectory": []
46 | }
--------------------------------------------------------------------------------
/config/webpack/Constants/Environment.ts:
--------------------------------------------------------------------------------
1 | enum Environment {
2 | DEVELOPMENT = 'development',
3 | PRODUCTION = 'production'
4 | }
5 |
6 | export default Environment;
--------------------------------------------------------------------------------
/config/webpack/Rules/typescriptRule.ts:
--------------------------------------------------------------------------------
1 | import {Rule} from 'webpack';
2 |
3 | const typescriptRule: Rule = {
4 | test: /\.tsx?$/,
5 | exclude: /node_modules/,
6 | loader: 'ts-loader'
7 | };
8 |
9 | export default typescriptRule;
--------------------------------------------------------------------------------
/config/webpack/config.ts:
--------------------------------------------------------------------------------
1 | import {resolve} from 'path';
2 | import {Configuration} from 'webpack';
3 |
4 | import Environment from './Constants/Environment';
5 | import typescriptRule from './Rules/typescriptRule';
6 |
7 | const config = (env: Environment = Environment.DEVELOPMENT): Configuration => {
8 | const isProductionEnvironment = env === Environment.PRODUCTION;
9 |
10 | return {
11 | mode: env,
12 | entry: './src/umd.ts',
13 | output: {
14 | filename: isProductionEnvironment ? 'easydropdown.js' : 'easydropdown.dev.js',
15 | path: resolve(__dirname, '..', '..', 'bundle'),
16 | library: 'easydropdown',
17 | libraryTarget: 'umd'
18 | },
19 | devtool: 'source-map',
20 | optimization: {
21 | minimize: isProductionEnvironment
22 | },
23 | resolve: {
24 | extensions: ['.ts', '.js']
25 | },
26 | module: {
27 | rules: [
28 | typescriptRule
29 | ]
30 | }
31 | };
32 | };
33 |
34 | export default config;
--------------------------------------------------------------------------------
/demos/03-groups.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Groups | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 03. Groups
20 |
21 |
22 |
35 |
36 |
37 |
38 |
Theme:
39 |
40 |
41 | |
42 | |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
57 |
58 |
--------------------------------------------------------------------------------
/demos/04-mixed-groups.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mixed Groups | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 04. Mixed Groups
20 |
21 |
22 |
37 |
38 |
39 |
40 |
Theme:
41 |
42 |
43 | |
44 | |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
59 |
60 |
--------------------------------------------------------------------------------
/demos/05-disabled-select.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Disabled Select | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 05. Disabled Select
20 |
21 |
22 |
28 |
29 |
30 |
31 |
Theme:
32 |
33 |
34 | |
35 | |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
50 |
51 |
--------------------------------------------------------------------------------
/demos/06-disabled-group.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Disabled Group | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 05. Disabled Group
20 |
21 |
22 |
35 |
36 |
37 |
38 |
Theme:
39 |
40 |
41 | |
42 | |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
57 |
58 |
--------------------------------------------------------------------------------
/demos/07-disabled-options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Disabled Options | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 07. Disabled Options
20 |
21 |
22 |
37 |
38 |
39 |
40 |
Theme:
41 |
42 |
43 | |
44 | |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
59 |
60 |
--------------------------------------------------------------------------------
/demos/10-form-validation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Form Validation | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | EasyDropDown Demo
19 |
20 | 10. Form Validation (Required Field)
21 |
22 |
41 |
42 |
43 |
Theme:
44 |
45 |
46 | |
47 | |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
62 |
63 |
--------------------------------------------------------------------------------
/demos/13-live-updates.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Live Updates | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | EasyDropDown Demo
19 |
20 | 13. Live Updates
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
Theme:
32 |
33 |
34 | |
35 | |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
54 |
55 |
--------------------------------------------------------------------------------
/demos/14-loop.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Loop | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 14. Loop
20 |
21 |
22 |
28 |
29 |
30 |
31 |
Theme:
32 |
33 |
34 | |
35 | |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
54 |
55 |
--------------------------------------------------------------------------------
/demos/15-callbacks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Callbacks | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EasyDropDown Demo
18 |
19 | 15. Callbacks
20 |
21 |
22 |
28 |
29 |
30 |
31 |
Theme:
32 |
33 |
34 | |
35 | |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
66 |
67 |
--------------------------------------------------------------------------------
/demos/16-programmatic-validation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Programmatic Validation | EasyDropDown Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | EasyDropDown Demo
19 |
20 | 16. Programmatic Validation
21 |
22 |
41 |
42 |
43 |
Theme:
44 |
45 |
46 | |
47 | |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
69 |
70 |
--------------------------------------------------------------------------------
/demos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | EasyDropDown Demos
10 |
11 |
12 | EasyDropDown Demos
13 |
14 |
15 | - Basic List
16 | - Basic List with Placeholder
17 | - Groups
18 | - Mixed Groups
19 | - Disabled Select
20 | - Disabled Group
21 | - Disabled Options
22 | - Pre-selected Value
23 | - Form Reset
24 | - Form Validation
25 | - Show Placeholder When Open
26 | - Collision Detection
27 | - Live Updates
28 | - Loop
29 | - Callbacks
30 | - Programmatic Validation
31 |
32 |
33 |
--------------------------------------------------------------------------------
/demos/scripts/optionAdder.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var addOptionButton, select;
3 |
4 | function initOptionAdder() {
5 | addOptionButton = document.getElementById('add-option');
6 | select = document.getElementById('demo-select');
7 |
8 | addOptionButton.addEventListener('click', handleClick);
9 | }
10 |
11 | function handleClick() {
12 | var totalOptions = select.options.length;
13 | var newOption = document.createElement('option');
14 |
15 | newOption.textContent = 'Option ' + totalOptions;
16 |
17 | select.appendChild(newOption);
18 | }
19 |
20 | document.addEventListener("DOMContentLoaded", initOptionAdder);
21 | })();
22 |
--------------------------------------------------------------------------------
/demos/scripts/submitHandler.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var form;
3 |
4 | function initSubmitHandler() {
5 | form = document.querySelector('form');
6 |
7 | form.addEventListener('submit', handleSubmit);
8 | }
9 |
10 | function handleSubmit(e) {
11 | e.preventDefault();
12 |
13 | alert('Valid!');
14 | }
15 |
16 | document.addEventListener("DOMContentLoaded", initSubmitHandler);
17 | })();
18 |
--------------------------------------------------------------------------------
/demos/scripts/themeSwitcher.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var themeSwitcher, themeSheet;
3 |
4 | function initThemeSwitcher() {
5 | themeSwitcher = document.querySelector('.theme-switcher');
6 | themeSheet = document.querySelector('#theme-sheet');
7 |
8 | themeSwitcher.addEventListener('click', handleThemeClick);
9 | }
10 |
11 | function handleThemeClick(e) {
12 | var all = document.querySelectorAll('[data-theme]');
13 | var target = e.target;
14 | var themeUrl = target.getAttribute('data-theme');
15 |
16 | if (!themeUrl) return;
17 |
18 | Array.prototype.forEach.call(all, function(link) {
19 | link.removeAttribute('class');
20 | });
21 |
22 | themeSheet.href = themeUrl;
23 |
24 | target.className = 'active';
25 | }
26 |
27 | document.addEventListener("DOMContentLoaded", initThemeSwitcher);
28 | })();
29 |
--------------------------------------------------------------------------------
/demos/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * The following styles pertain to the demo elements only, and are
3 | * not related to or necessary for any EasyDropDown functionality.
4 | */
5 |
6 | * {
7 | margin: 0;
8 | padding: 0;
9 | box-sizing: border-box;
10 | }
11 |
12 | body {
13 | box-sizing: border-box;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | background: #fafafa;
18 | font-family: 'Helvetica Neue', arial, helvetica, sans-serif;
19 | min-height: 100vh;
20 | -webkit-font-smoothing: antialiased;
21 | -moz-osx-font-smoothing: grayscale;
22 | }
23 |
24 | .main {
25 | padding: 16px;
26 | flex: 1;
27 | }
28 |
29 | .main--at-bottom {
30 | display: flex;
31 | flex-direction: column;
32 | justify-content: flex-end;
33 | }
34 |
35 | .heading,
36 | .sub-heading,
37 | .demo-container {
38 | width: calc(100vw - 20px);
39 | max-width: 400px;
40 | }
41 |
42 | .heading,
43 | .sub-heading {
44 | font-weight: 600;
45 | letter-spacing: -0.02em;
46 | }
47 |
48 | .heading {
49 | font-size: 24px;
50 | margin-bottom: 0.8em;
51 | }
52 |
53 | .sub-heading {
54 | font-size: 14px;
55 | margin-bottom: 0.5em;
56 | }
57 |
58 | .demo-container {
59 | display: flex;
60 | height: 150px;
61 | flex-direction: column;
62 | align-items: center;
63 | background: white;
64 | justify-content: center;
65 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.03);
66 | }
67 |
68 | .demo-container > select {
69 | visibility: hidden;
70 | }
71 |
72 | .label {
73 | font-size: 13px;
74 | width: 180px;
75 | font-weight: 700;
76 | margin-bottom: 5px;
77 | }
78 |
79 | .edd-body {
80 | opacity: 0;
81 | }
82 |
83 | .button {
84 | background-color: #333;
85 | padding: 10px;
86 | min-width: 180px;
87 | color: white;
88 | font-weight: 700;
89 | font-size: 14px;
90 | border: 3px solid #333;
91 | border-radius: 0;
92 | margin-top: 20px;
93 | cursor: pointer;
94 | outline: 0 none;
95 | transition: background-color 150ms, color 150ms;
96 | }
97 |
98 | .button:hover {
99 | background-color: white;
100 | color: #333;
101 | }
102 |
103 | .theme-switcher {
104 | padding: 8px 0;
105 | color: #ddd;
106 | display: flex;
107 | justify-content: space-between;
108 | }
109 |
110 | .theme-switcher-options * {
111 | display: inline;
112 | }
113 |
114 | .theme-switcher-label {
115 | color: black;
116 | }
117 |
118 | .theme-switcher button {
119 | color: #333;
120 | cursor: pointer;
121 | background: transparent;
122 | font-family: 'Helvetica Neue', arial, helvetica, sans-serif;
123 | font-size: 16px;
124 | border: 0 none;
125 | outline: 0 none;
126 | }
127 |
128 | .theme-switcher .active {
129 | font-weight: 600;
130 | cursor: pointer;
131 | }
132 |
133 | .footer {
134 | width: 100%;
135 | background: #2a2a2a;
136 | display: flex;
137 | justify-content: space-between;
138 | padding: 16px;
139 | font-size: 12px;
140 | color: #fff;
141 | }
142 |
143 | .footer a {
144 | color: #ccc;
145 | text-decoration: none;
146 | transition: color 150ms;
147 | }
148 |
149 | .footer a:hover {
150 | color: #fff;
151 | }
--------------------------------------------------------------------------------
/demos/themes/README.md:
--------------------------------------------------------------------------------
1 | # Themes
2 |
3 | These example themes demonstrate 3 varied but idiomatic approaches to styling the dropdown menu.
4 |
5 | For maximum consumability, they are written in vanilla CSS, and as such do not include any niceties such as variables or vendor prefixes.
6 |
7 | If you plan to use or adapt any of these themes for your project, it is your responsibility to ensure the appropriate vendor prefixes (e.g for CSS transforms) are applied for your desired browser support.
8 |
9 | We recommend [post-CSS](http://postcss.org/) for this purpose.
--------------------------------------------------------------------------------
/demos/themes/beanstalk.css:
--------------------------------------------------------------------------------
1 | @import '//fonts.googleapis.com/css?family=Open+Sans:400,600';
2 |
3 | .edd-root,
4 | .edd-root *,
5 | .edd-root *::before,
6 | .edd-root *::after {
7 | margin: 0;
8 | padding: 0;
9 | box-sizing: border-box;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | .edd-root {
15 | display: inline-block;
16 | position: relative;
17 | width: 180px;
18 | user-select: none;
19 | font-family: 'Open Sans', arial, helvetica, sans-serif;
20 | font-size: 16px;
21 | color: #333;
22 | }
23 |
24 | .edd-root-disabled {
25 | color: #ccc;
26 | cursor: not-allowed;
27 | }
28 |
29 | .edd-head {
30 | position: relative;
31 | overflow: hidden;
32 | border: 1px solid #eee;
33 | transition: box-shadow 200ms, border-color 150ms;
34 | background: white;
35 | }
36 |
37 | .edd-head,
38 | .edd-body {
39 | border-radius: 4px;
40 | }
41 |
42 | .edd-root-focused .edd-head {
43 | box-shadow: 0 0 5px rgba(105, 215, 255, 0.4);
44 | }
45 |
46 | .edd-root-invalid .edd-head {
47 | box-shadow: 0 0 5px rgba(255, 105, 105, 0.671);
48 | }
49 |
50 | .edd-root:not(.edd-root-disabled):not(.edd-root-open) .edd-head:hover {
51 | border-color: #ccc;
52 | }
53 |
54 | .edd-value {
55 | width: calc(100% - 50px);
56 | display: inline-block;
57 | vertical-align: middle;
58 | margin: 8px 0 8px 8px;
59 | border-right: 1px solid #eee;
60 | }
61 |
62 | .edd-arrow {
63 | position: absolute;
64 | width: 18px;
65 | height: 10px;
66 | top: calc(50% - 5px);
67 | right: calc(24px - 9px);
68 | transition: transform 150ms;
69 | pointer-events: none;
70 | }
71 |
72 | .edd-arrow::before {
73 | content: '';
74 | position: absolute;
75 | width: 13px;
76 | height: 13px;
77 | border-right: 1px solid currentColor;
78 | border-bottom: 1px solid currentColor;
79 | top: -5px;
80 | right: 0;
81 | transform: rotate(45deg);
82 | transform-origin: 50% 25%;
83 | }
84 |
85 | .edd-root-open .edd-arrow {
86 | transform: rotate(180deg);
87 | }
88 |
89 | .edd-value,
90 | .edd-option,
91 | .edd-group-label {
92 | white-space: nowrap;
93 | text-overflow: ellipsis;
94 | overflow: hidden;
95 | }
96 |
97 | .edd-root:not(.edd-root-disabled) .edd-value,
98 | .edd-option {
99 | cursor: pointer;
100 | }
101 |
102 | .edd-select {
103 | position: absolute;
104 | opacity: 0;
105 | width: 100%;
106 | left: -100%;
107 | top: 0;
108 | }
109 |
110 | .edd-root-native .edd-select {
111 | left: 0;
112 | top: 0;
113 | width: 100%;
114 | height: 100%;
115 | }
116 |
117 | .edd-body {
118 | opacity: 0;
119 | position: absolute;
120 | left: 0;
121 | right: 0;
122 | border: 1px solid #eee;
123 | pointer-events: none;
124 | overflow: hidden;
125 | margin: 8px 0;
126 | z-index: 999;
127 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
128 | transform: scale(0.95);
129 | background: white;
130 | }
131 |
132 | .edd-root-open .edd-body {
133 | opacity: 1;
134 | pointer-events: all;
135 | transform: scale(1);
136 | transition: opacity 200ms, transform 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
137 | }
138 |
139 | .edd-root-open-above .edd-body {
140 | bottom: 100%;
141 | }
142 |
143 | .edd-root-open-below .edd-body {
144 | top: 100%;
145 | }
146 |
147 | .edd-items-list {
148 | overflow: auto;
149 | max-height: 0;
150 | transition: max-height 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
151 | -webkit-overflow-scrolling: touch;
152 | }
153 |
154 | .edd-group-label {
155 | font-size: 11px;
156 | text-transform: uppercase;
157 | font-weight: bold;
158 | letter-spacing: 0.1em;
159 | padding: 12px 8px 4px;
160 | color: #999;
161 | }
162 |
163 | .edd-group-has-label {
164 | border-bottom: 1px solid #eee;
165 | }
166 |
167 | .edd-option {
168 | padding: 4px 8px;
169 | }
170 |
171 | .edd-group-has-label .edd-option {
172 | padding-left: 20px;
173 | }
174 |
175 | .edd-option-selected {
176 | font-weight: bold;
177 | }
178 |
179 | .edd-option-focused:not(.edd-option-disabled) {
180 | color: #4ac5f1;
181 | }
182 |
183 | .edd-option-disabled,
184 | .edd-group-disabled .edd-option {
185 | cursor: default;
186 | color: #ccc;
187 | }
188 |
189 | .edd-gradient-top,
190 | .edd-gradient-bottom {
191 | content: '';
192 | position: absolute;
193 | left: 2px;
194 | right: 2px;
195 | height: 32px;
196 | background-image:
197 | linear-gradient(
198 | 0deg,
199 | rgba(255, 255, 255, 0) 0%,
200 | rgba(255, 255, 255, 1) 40%,
201 | rgba(255, 255, 255, 1) 60%,
202 | rgba(255, 255, 255, 0) 100%
203 | );
204 | background-repeat: repeat-x;
205 | background-size: 100% 200%;
206 | pointer-events: none;
207 | transition: opacity 100ms;
208 | opacity: 0;
209 | }
210 |
211 | .edd-gradient-top {
212 | background-position: bottom;
213 | top: 0;
214 | }
215 |
216 | .edd-gradient-bottom {
217 | background-position: top;
218 | bottom: 0;
219 | }
220 |
221 | .edd-body-scrollable .edd-gradient-top,
222 | .edd-body-scrollable .edd-gradient-bottom {
223 | opacity: 1;
224 | }
225 |
226 | .edd-body-scrollable.edd-body-at-top .edd-gradient-top,
227 | .edd-body-scrollable.edd-body-at-bottom .edd-gradient-bottom {
228 | opacity: 0;
229 | }
--------------------------------------------------------------------------------
/demos/themes/flax.css:
--------------------------------------------------------------------------------
1 | @import '//fonts.googleapis.com/css?family=Roboto:300,400"';
2 |
3 | .edd-root,
4 | .edd-root *,
5 | .edd-root *::before,
6 | .edd-root *::after {
7 | margin: 0;
8 | padding: 0;
9 | box-sizing: border-box;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | .edd-root {
15 | display: inline-block;
16 | position: relative;
17 | width: 180px;
18 | user-select: none;
19 | font-family: 'Roboto', arial, helvetica, sans-serif;
20 | font-weight: 300;
21 | font-size: 16px;
22 | color: #333;
23 | }
24 |
25 | .edd-root-disabled {
26 | color: #ccc;
27 | cursor: not-allowed;
28 | }
29 |
30 | .edd-root::after {
31 | content: '';
32 | position: absolute;
33 | bottom: 0;
34 | left: 0;
35 | right: 0;
36 | height: 2px;
37 | background: #45bce7;
38 | transition: transform 150ms ease-out;
39 | transform: scaleX(0);
40 | }
41 |
42 | .edd-root.edd-root-focused::after,
43 | .edd-root.edd-root-invalid::after {
44 | transform: scaleX(1);
45 | }
46 |
47 | .edd-root.edd-root-invalid::after {
48 | background: rgb(255, 105, 105);
49 | }
50 |
51 | .edd-head {
52 | position: relative;
53 | overflow: hidden;
54 | border-bottom: 1px solid #ddd;
55 | transition: border-color 200ms;
56 | }
57 |
58 | .edd-root:not(.edd-root-disabled) .edd-head:hover {
59 | border-bottom-color: #aaa;
60 | }
61 |
62 | .edd-value {
63 | width: 100%;
64 | display: inline-block;
65 | vertical-align: middle;
66 | padding: 8px 25px 8px 0;
67 | }
68 |
69 | .edd-arrow {
70 | position: absolute;
71 | width: 14px;
72 | height: 10px;
73 | top: calc(50% - 5px);
74 | right: 3px;
75 | transition: transform 150ms;
76 | pointer-events: none;
77 | color: #666;
78 | }
79 |
80 | .edd-root-disabled .edd-arrow {
81 | color: #ccc;
82 | }
83 |
84 | .edd-arrow::before {
85 | content: '';
86 | position: absolute;
87 | width: 8px;
88 | height: 8px;
89 | border-right: 2px solid currentColor;
90 | border-bottom: 2px solid currentColor;
91 | top: 0;
92 | right: 2px;
93 | transform: rotate(45deg);
94 | transform-origin: 50% 25%;
95 | }
96 |
97 | .edd-root-open .edd-arrow {
98 | transform: rotate(180deg);
99 | }
100 |
101 | .edd-value,
102 | .edd-option,
103 | .edd-group-label {
104 | white-space: nowrap;
105 | text-overflow: ellipsis;
106 | overflow: hidden;
107 | }
108 |
109 | .edd-root:not(.edd-root-disabled) .edd-value,
110 | .edd-option {
111 | cursor: pointer;
112 | }
113 |
114 | .edd-select {
115 | position: absolute;
116 | opacity: 0;
117 | width: 100%;
118 | left: -100%;
119 | top: 0;
120 | }
121 |
122 | .edd-root-native .edd-select {
123 | left: 0;
124 | top: 0;
125 | width: 100%;
126 | height: 100%;
127 | }
128 |
129 | .edd-body {
130 | opacity: 0;
131 | position: absolute;
132 | left: 0;
133 | right: 0;
134 | pointer-events: none;
135 | overflow: hidden;
136 | z-index: 999;
137 | background: white;
138 | box-shadow: 0 0 6px rgba(0, 0, 0, 0.08);
139 | border: 1px solid #eee;
140 | border-top: 0;
141 | border-right: 0;
142 | }
143 |
144 | .edd-root-open .edd-body {
145 | opacity: 1;
146 | pointer-events: all;
147 | transform: scale(1);
148 | transition: opacity 200ms, transform 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
149 | }
150 |
151 | .edd-root-open-above .edd-body {
152 | bottom: 100%;
153 | }
154 |
155 | .edd-root-open-below .edd-body {
156 | top: 100%;
157 | }
158 |
159 | .edd-items-list {
160 | overflow: auto;
161 | max-height: 0;
162 | transition: max-height 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
163 | -webkit-overflow-scrolling: touch;
164 | }
165 |
166 | .edd-items-list::-webkit-scrollbar {
167 | width: 12px;
168 | }
169 |
170 | .edd-items-list::-webkit-scrollbar-track {
171 | background: #efefef;
172 | }
173 |
174 | .edd-items-list::-webkit-scrollbar-thumb {
175 | background: #ccc;
176 | }
177 |
178 | .edd-group-label {
179 | font-size: 13px;
180 | padding: 4px 8px 4px 0;
181 | color: #555;
182 | font-weight: 600;
183 | }
184 |
185 | .edd-group-has-label {
186 | padding-left: 22px;
187 | }
188 |
189 | .edd-option {
190 | position: relative;
191 | padding: 4px 8px 4px 22px;
192 | }
193 |
194 | .edd-option-selected {
195 | font-weight: 400;
196 | }
197 |
198 | .edd-option-selected::before {
199 | content: '';
200 | position: absolute;
201 | width: 8px;
202 | height: 4px;
203 | border-bottom: 2px solid #4ac5f1;
204 | border-left: 2px solid #4ac5f1;
205 | left: 6px;
206 | top: calc(50% - 4px);
207 | transform: rotate(-45deg);
208 | }
209 |
210 | .edd-option-focused:not(.edd-option-disabled) {
211 | color: #4ac5f1;
212 | }
213 |
214 | .edd-option-disabled,
215 | .edd-group-disabled .edd-option {
216 | cursor: default;
217 | color: #ccc;
218 | }
219 |
220 | .edd-gradient-top,
221 | .edd-gradient-bottom {
222 | content: '';
223 | position: absolute;
224 | left: 2px;
225 | right: 12px;
226 | height: 32px;
227 | background-image:
228 | linear-gradient(
229 | 0deg,
230 | rgba(255, 255, 255, 0) 0%,
231 | rgba(255, 255, 255, 1) 40%,
232 | rgba(255, 255, 255, 1) 60%,
233 | rgba(255, 255, 255, 0) 100%
234 | );
235 | background-repeat: repeat-x;
236 | background-size: 100% 200%;
237 | pointer-events: none;
238 | transition: opacity 100ms;
239 | opacity: 0;
240 | }
241 |
242 | .edd-gradient-top {
243 | background-position: bottom;
244 | top: 0;
245 | }
246 |
247 | .edd-gradient-bottom {
248 | background-position: top;
249 | bottom: 0;
250 | }
251 |
252 | .edd-body-scrollable .edd-gradient-top,
253 | .edd-body-scrollable .edd-gradient-bottom {
254 | opacity: 1;
255 | }
256 |
257 | .edd-body-scrollable.edd-body-at-top .edd-gradient-top,
258 | .edd-body-scrollable.edd-body-at-bottom .edd-gradient-bottom {
259 | opacity: 0;
260 | }
--------------------------------------------------------------------------------
/demos/themes/ivy.css:
--------------------------------------------------------------------------------
1 | .edd-root,
2 | .edd-root *,
3 | .edd-root *::before,
4 | .edd-root *::after {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 |
10 | .edd-root {
11 | display: inline-block;
12 | position: relative;
13 | width: 180px;
14 | user-select: none;
15 | font-family: 'Helvetica Neue', arial, helvetica, sans-serif;
16 | font-size: 16px;
17 | font-weight: 300;
18 | color: #333;
19 | }
20 |
21 | .edd-root-disabled {
22 | color: #ccc;
23 | cursor: not-allowed;
24 | }
25 |
26 | .edd-head {
27 | position: relative;
28 | overflow: hidden;
29 | border: 1px solid #eee;
30 | transition: box-shadow 200ms;
31 | background: white;
32 | }
33 |
34 | .edd-head,
35 | .edd-body {
36 | border-radius: 20px;
37 | }
38 |
39 | .edd-root-focused .edd-head {
40 | border-color: blue;
41 | }
42 |
43 | .edd-root-invalid .edd-head {
44 | border-color: #ff6969;
45 | }
46 |
47 | .edd-value {
48 | width: 100%;
49 | display: inline-block;
50 | vertical-align: middle;
51 | padding: 10px 35px 10px 10px;
52 | }
53 |
54 | .edd-arrow {
55 | position: absolute;
56 | width: 18px;
57 | height: 10px;
58 | top: calc(50% - 5px);
59 | right: calc(25px - 9px);
60 | transition: transform 150ms;
61 | pointer-events: none;
62 | color: #888;
63 | }
64 |
65 | .edd-arrow::before {
66 | content: '';
67 | position: absolute;
68 | width: 13px;
69 | height: 13px;
70 | border-right: 1px solid currentColor;
71 | border-bottom: 1px solid currentColor;
72 | top: -5px;
73 | right: 0;
74 | transform: rotate(45deg);
75 | transform-origin: 50% 25%;
76 | }
77 |
78 | .edd-root-open .edd-arrow {
79 | transform: rotate(180deg);
80 | }
81 |
82 | .edd-root-open .edd-arrow,
83 | .edd-root:not(.edd-root-disabled):not(.edd-root-open) .edd-head:hover .edd-arrow {
84 | color: blue;
85 | }
86 |
87 | .edd-value,
88 | .edd-option,
89 | .edd-group-label {
90 | white-space: nowrap;
91 | text-overflow: ellipsis;
92 | overflow: hidden;
93 | }
94 |
95 | .edd-root:not(.edd-root-disabled) .edd-value,
96 | .edd-option {
97 | cursor: pointer;
98 | }
99 |
100 | .edd-select {
101 | position: absolute;
102 | opacity: 0;
103 | width: 100%;
104 | left: -100%;
105 | top: 0;
106 | }
107 |
108 | .edd-root-native .edd-select {
109 | left: 0;
110 | top: 0;
111 | width: 100%;
112 | height: 100%;
113 | }
114 |
115 | .edd-body {
116 | opacity: 0;
117 | position: absolute;
118 | left: 0;
119 | right: 0;
120 | border: 1px solid #eee;
121 | pointer-events: none;
122 | overflow: hidden;
123 | margin: 8px 0;
124 | z-index: 999;
125 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
126 | transform: scale(0.95);
127 | background: white;
128 | }
129 |
130 | .edd-root-open .edd-body {
131 | opacity: 1;
132 | pointer-events: all;
133 | transform: scale(1);
134 | transition: opacity 200ms, transform 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
135 | }
136 |
137 | .edd-root-open-above .edd-body {
138 | bottom: 100%;
139 | }
140 |
141 | .edd-root-open-below .edd-body {
142 | top: 100%;
143 | }
144 |
145 | .edd-items-list {
146 | overflow: auto;
147 | max-height: 0;
148 | transition: max-height 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
149 | -webkit-overflow-scrolling: touch;
150 | }
151 |
152 | .edd-group-label {
153 | font-size: 12px;
154 | font-weight: 400;
155 | padding: 12px 10px 4px;
156 | }
157 |
158 | .edd-option {
159 | padding: 6px 10px;
160 | border-bottom: 1px solid #eee;
161 | transition: background-color 250ms, color 250ms, border-color 250ms;
162 | }
163 |
164 | .edd-group-has-label .edd-option {
165 | padding-left: 14px;
166 | }
167 |
168 | .edd-option-selected {
169 | font-weight: 400;
170 | color: blue;
171 | }
172 |
173 | .edd-option-focused:not(.edd-option-disabled) {
174 | background: blue;
175 | border-bottom-color: blue;
176 | color: white;
177 | }
178 |
179 | .edd-option-disabled,
180 | .edd-group-disabled .edd-option {
181 | cursor: default;
182 | color: #ccc;
183 | }
--------------------------------------------------------------------------------
/demos/themes/theme.css.d.ts:
--------------------------------------------------------------------------------
1 | export const root: string;
2 | export const rootOpen: string;
3 | export const rootOpenAbove: string;
4 | export const rootOpenBelow: string;
5 | export const rootDisabled: string;
6 | export const rootInvalid: string;
7 | export const rootFocused: string;
8 | export const rootHasValue: string;
9 | export const rootNative: string;
10 | export const head: string;
11 | export const value: string;
12 | export const arrow: string;
13 | export const select: string;
14 | export const body: string;
15 | export const bodyScrollable: string;
16 | export const bodyAtTop: string;
17 | export const bodyAtBottom: string;
18 | export const gradientTop: string;
19 | export const gradientBottom: string;
20 | export const itemsList: string;
21 | export const group: string;
22 | export const groupDisabled: string;
23 | export const groupHasLabel: string;
24 | export const groupLabel: string;
25 | export const option: string;
26 | export const optionDisabled: string;
27 | export const optionFocused: string;
28 | export const optionSelected: string;
--------------------------------------------------------------------------------
/docs/easydropdown-anatomy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patrickkunka/easydropdown/caa479bd3647eb9e689b9d428f5ba41d4988b8ae/docs/easydropdown-anatomy.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "easydropdown",
3 | "version": "4.2.0",
4 | "description": "A lightweight library for building beautiful styleable select elements",
5 | "author": "KunkaLabs Limited",
6 | "private": false,
7 | "license": "Apache-2.0",
8 | "main": "./dist/index.js",
9 | "types": "./dist/index.d.ts",
10 | "browser": "./dist/index.js",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/patrickkunka/easydropdown/"
14 | },
15 | "scripts": {
16 | "test": "mocha --opts ./config/mocha/mocha.opts --exit",
17 | "test:watch": "npm run test -- --watch",
18 | "test:cover": "nyc npm run test",
19 | "test:size": "bundlesize",
20 | "lint": "npm run lint:ts && npm run lint:css",
21 | "lint:ts": "tslint --project tsconfig.json -c './config/tslint/tslint.json' './src/**/*.ts' './src/**/*.test.ts'",
22 | "lint:css": "stylelint --config './config/stylelint/.stylelintrc.json' './demos/**/*.css'",
23 | "watch": "tsc --watch",
24 | "build": "tsc",
25 | "bundle": "webpack --config ./config/webpack/config.ts",
26 | "bundle:watch": "npm run bundle -- --watch",
27 | "bundle:build": "npm run bundle -- --env=production && npm run copy:bundle",
28 | "copy:bundle": "cp ./bundle/easydropdown.js ./demos/easydropdown.js && cp ./bundle/easydropdown.js.map ./demos/easydropdown.js.map",
29 | "prepublishOnly": "npm run test && npm run lint && npm run bundle:build && npm run build",
30 | "coveralls": "npm run test:cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls"
31 | },
32 | "nyc": {
33 | "extends": "./config/nyc/.nycrc.json"
34 | },
35 | "bundlesize": [
36 | {
37 | "path": "./bundle/easydropdown.js",
38 | "maxSize": "9.17 kB"
39 | }
40 | ],
41 | "dependencies": {
42 | "custom-event-polyfill": "1.0.6",
43 | "helpful-merge": "0.2.0"
44 | },
45 | "devDependencies": {
46 | "@types/chai": "4.1.7",
47 | "@types/mocha": "5.2.5",
48 | "@types/node": "10.12.12",
49 | "@types/sinon": "7.0.4",
50 | "bundlesize": "0.17.1",
51 | "chai": "4.2.0",
52 | "chai-shallow-deep-equal": "1.4.6",
53 | "coveralls": "3.0.3",
54 | "istanbul": "0.4.5",
55 | "jsdom": "13.0.0",
56 | "jsdom-global": "3.0.2",
57 | "mocha": "5.2.0",
58 | "nyc": "13.3.0",
59 | "sinon": "7.2.3",
60 | "source-map-support": "0.5.0",
61 | "stylelint": "9.10.1",
62 | "stylelint-config-standard": "18.2.0",
63 | "ts-loader": "5.3.0",
64 | "ts-node": "7.0.1",
65 | "tsconfig-paths": "3.3.1",
66 | "tslint": "5.14.0",
67 | "tslint-eslint-rules": "5.4.0",
68 | "typescript": "3.1.6",
69 | "webpack": "4.29.6",
70 | "webpack-cli": "3.1.0"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Components/arrow.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 |
3 | const arrow = (_, classNames: ClassNames) => ``;
4 |
5 | export default arrow;
--------------------------------------------------------------------------------
/src/Components/body.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 | import composeClassName from '../Shared/Util/composeClassName';
3 | import State from '../State/State';
4 |
5 | import group from './group';
6 |
7 | function body(state: State, classNames: ClassNames): string {
8 | const className = composeClassName([
9 | classNames.body,
10 | [state.isAtTop, classNames.bodyAtTop],
11 | [state.isAtBottom, classNames.bodyAtBottom],
12 | [state.isScrollable, classNames.bodyScrollable]
13 | ]);
14 |
15 | const styleAttr = state.isOpen ?
16 | `style="max-height: ${state.maxBodyHeight}px;"` : '';
17 |
18 | return (`
19 |
25 |
28 | ${state.groups.map(groupState => group(groupState, state, classNames)).join('')}
29 |
30 |
31 |
32 |
33 | `);
34 | }
35 |
36 | export default body;
--------------------------------------------------------------------------------
/src/Components/group.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 | import composeClassName from '../Shared/Util/composeClassName';
3 | import Group from '../State/Group';
4 | import State from '../State/State';
5 |
6 | import option from './option';
7 |
8 | const group = (groupState: Group, state: State, classNames: ClassNames) => {
9 | const className = composeClassName([
10 | classNames.group,
11 | [groupState.isDisabled, classNames.groupDisabled],
12 | [groupState.hasLabel, classNames.groupHasLabel]
13 | ]);
14 |
15 | return (`
16 |
17 | ${groupState.hasLabel ?
18 | `
${groupState.label}
` : ''
19 | }
20 | ${groupState.options.map(optionState => option(optionState, state, classNames)).join('')}
21 |
22 | `);
23 | };
24 |
25 | export default group;
--------------------------------------------------------------------------------
/src/Components/head.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 | import State from '../State/State';
3 |
4 | import arrow from './arrow';
5 | import value from './value';
6 |
7 | const head = (state: State, classNames: ClassNames) => (`
8 |
9 | ${value(state, classNames)}
10 | ${arrow(state, classNames)}
11 |
12 |
13 | `);
14 |
15 | export default head;
--------------------------------------------------------------------------------
/src/Components/option.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 | import composeClassName from '../Shared/Util/composeClassName';
3 | import Option from '../State/Option';
4 | import State from '../State/State';
5 |
6 | function option(optionState: Option, state: State, classNames: ClassNames): string {
7 | const isSelected = state.selectedOption === optionState;
8 |
9 | const className = composeClassName([
10 | classNames.option,
11 | [isSelected, classNames.optionSelected],
12 | [optionState === state.focusedOption, classNames.optionFocused],
13 | [optionState.isDisabled, classNames.optionDisabled]
14 | ]);
15 |
16 | return (`
17 |
25 | ${optionState.label}
26 |
27 | `);
28 | }
29 |
30 | export default option;
--------------------------------------------------------------------------------
/src/Components/root.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 | import composeClassName from '../Shared/Util/composeClassName';
3 | import State from '../State/State';
4 |
5 | import body from './body';
6 | import head from './head';
7 |
8 | const root = (state: State, classNames: ClassNames) => {
9 | const className = composeClassName([
10 | classNames.root,
11 | [state.isDisabled, classNames.rootDisabled],
12 | [state.isInvalid, classNames.rootInvalid],
13 | [state.isOpen, classNames.rootOpen],
14 | [state.isFocused, classNames.rootFocused],
15 | [state.hasValue, classNames.rootHasValue],
16 | [state.isOpenAbove, classNames.rootOpenAbove],
17 | [state.isOpenBelow, classNames.rootOpenBelow],
18 | [state.isUseNativeMode, classNames.rootNative]
19 | ]);
20 |
21 | return (`
22 |
31 | ${head(state, classNames)}
32 | ${state.isUseNativeMode ? '' : body(state, classNames)}
33 |
34 | `);
35 | };
36 |
37 | export default root;
--------------------------------------------------------------------------------
/src/Components/value.ts:
--------------------------------------------------------------------------------
1 | import ClassNames from '../Config/ClassNames';
2 | import State from '../State/State';
3 |
4 | const value = (state: State, classNames: ClassNames) => {
5 | return (`
6 |
11 | ${state.humanReadableValue}
12 |
13 | `);
14 | };
15 |
16 | export default value;
--------------------------------------------------------------------------------
/src/Config/Behavior.ts:
--------------------------------------------------------------------------------
1 | import IBehavior from './Interfaces/IBehavior';
2 |
3 | class Behavior implements IBehavior {
4 | public showPlaceholderWhenOpen: boolean = false;
5 | public openOnFocus: boolean = false;
6 | public closeOnSelect: boolean = true;
7 | public useNativeUiOnMobile: boolean = true;
8 | public loop: boolean = false;
9 | public clampMaxVisibleItems: boolean = true;
10 | public liveUpdates: boolean = false;
11 | public maxVisibleItems: number = 15;
12 |
13 | constructor() {
14 | Object.seal(this);
15 | }
16 | }
17 |
18 | export default Behavior;
--------------------------------------------------------------------------------
/src/Config/Callbacks.ts:
--------------------------------------------------------------------------------
1 | import ICallback from './Interfaces/ICallback';
2 | import ISelectCallback from './Interfaces/ISelectCallback';
3 |
4 | class Callbacks {
5 | public onOpen: ICallback = null;
6 | public onClose: ICallback = null;
7 | public onSelect: ISelectCallback = null;
8 | public onOptionClick: ISelectCallback = null;
9 |
10 | constructor() {
11 | Object.seal(this);
12 | }
13 | }
14 |
15 | export default Callbacks;
--------------------------------------------------------------------------------
/src/Config/ClassNames.ts:
--------------------------------------------------------------------------------
1 | import IClassNames from './Interfaces/IClassNames';
2 |
3 | class ClassNames implements IClassNames {
4 | public root: string = 'edd-root';
5 | public rootOpen: string = 'edd-root-open';
6 | public rootOpenAbove: string = 'edd-root-open-above';
7 | public rootOpenBelow: string = 'edd-root-open-below';
8 | public rootDisabled: string = 'edd-root-disabled';
9 | public rootInvalid: string = 'edd-root-invalid';
10 | public rootFocused: string = 'edd-root-focused';
11 | public rootHasValue: string = 'edd-root-has-value';
12 | public rootNative: string = 'edd-root-native';
13 | public gradientTop: string = 'edd-gradient-top';
14 | public gradientBottom: string = 'edd-gradient-bottom';
15 | public head: string = 'edd-head';
16 | public value: string = 'edd-value';
17 | public arrow: string = 'edd-arrow';
18 | public select: string = 'edd-select';
19 | public body: string = 'edd-body';
20 | public bodyScrollable: string = 'edd-body-scrollable';
21 | public bodyAtTop: string = 'edd-body-at-top';
22 | public bodyAtBottom: string = 'edd-body-at-bottom';
23 | public itemsList: string = 'edd-items-list';
24 | public group: string = 'edd-group';
25 | public groupDisabled: string = 'edd-group-disabled';
26 | public groupHasLabel: string = 'edd-group-has-label';
27 | public groupLabel: string = 'edd-group-label';
28 | public option: string = 'edd-option';
29 | public optionDisabled: string = 'edd-option-disabled';
30 | public optionFocused: string = 'edd-option-focused';
31 | public optionSelected: string = 'edd-option-selected';
32 |
33 | constructor() {
34 | Object.seal(this);
35 | }
36 | }
37 |
38 | export default ClassNames;
--------------------------------------------------------------------------------
/src/Config/Config.ts:
--------------------------------------------------------------------------------
1 | import Behavior from './Behavior';
2 | import Callbacks from './Callbacks';
3 | import ClassNames from './ClassNames';
4 | import IConfig from './Interfaces/IConfig';
5 |
6 | class Config implements IConfig {
7 | public callbacks = new Callbacks();
8 | public classNames = new ClassNames();
9 | public behavior = new Behavior();
10 |
11 | constructor() {
12 | Object.seal(this);
13 | }
14 | }
15 |
16 | export default Config;
--------------------------------------------------------------------------------
/src/Config/Interfaces/IBehavior.ts:
--------------------------------------------------------------------------------
1 | interface IBehavior {
2 | /**
3 | * A boolean dictating whether or not to further
4 | * reduce the `maxVisibleItems` value of the dropdown
5 | * menu when a collision occurs.
6 | *
7 | * @default true
8 | */
9 |
10 | clampMaxVisibleItems?: boolean;
11 |
12 | /**
13 | * A boolean dictating whether or not the dropdown
14 | * should close when a value is selected.
15 | *
16 | * @default true
17 | */
18 |
19 | closeOnSelect?: boolean;
20 |
21 | /**
22 | * A boolean dictating whether or not the dropdown should
23 | * watch for updates to the underyling `