7 | ├── .babelrc ├── .dockerignore ├── .editorconfig ├── .eslintrc ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .jsdoc.json ├── .npmignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── docker-compose.yml ├── gulpfile.js ├── logo.png ├── package-lock.json ├── package.json ├── src ├── docs │ ├── example-group.hbs │ ├── example.hbs │ ├── examples.json │ ├── examples │ │ ├── Basics.hbs │ │ ├── p02_Advanced_Examples.hbs │ │ ├── p03_Colorpicker_Events.hbs │ │ ├── p04_Colorpicker_Extensions.hbs │ │ └── p05_OlderVersions.hbs │ └── jsdoc-template │ │ ├── LICENSE │ │ ├── README.md │ │ ├── publish.js │ │ ├── static │ │ ├── logo.png │ │ ├── scripts │ │ │ ├── jsdoc.js │ │ │ └── prettify │ │ │ │ ├── Apache-License-2.0.txt │ │ │ │ ├── lang-css.js │ │ │ │ └── prettify.js │ │ └── styles │ │ │ ├── jsdoc-custom.css │ │ │ ├── jsdoc-default.css │ │ │ ├── prettify-jsdoc.css │ │ │ └── prettify-tomorrow.css │ │ └── tmpl │ │ ├── augments.tmpl │ │ ├── container.tmpl │ │ ├── details.tmpl │ │ ├── example.tmpl │ │ ├── examples.tmpl │ │ ├── exceptions.tmpl │ │ ├── layout.tmpl │ │ ├── mainpage.tmpl │ │ ├── members.tmpl │ │ ├── method.tmpl │ │ ├── params.tmpl │ │ ├── properties.tmpl │ │ ├── returns.tmpl │ │ ├── source.tmpl │ │ ├── tutorial.tmpl │ │ └── type.tmpl ├── img │ └── logo.ai ├── js │ ├── AddonHandler.js │ ├── ColorHandler.js │ ├── ColorItem.js │ ├── Colorpicker.js │ ├── Extension.js │ ├── InputHandler.js │ ├── PickerHandler.js │ ├── PopupHandler.js │ ├── SliderHandler.js │ ├── extensions │ │ ├── Debugger.js │ │ ├── Palette.js │ │ ├── Preview.js │ │ ├── Swatches.js │ │ └── index.js │ ├── options.js │ └── plugin.js └── sass │ └── colorpicker.scss ├── tests └── Color-test.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env" 5 | ] 6 | ], 7 | "plugins": [ 8 | "add-module-exports" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /node_modules/ 4 | /gh-pages/ 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "globals": { 8 | "document": false, 9 | "escape": false, 10 | "navigator": false, 11 | "unescape": false, 12 | "window": false, 13 | "describe": true, 14 | "before": true, 15 | "it": true, 16 | "expect": true, 17 | "sinon": true 18 | }, 19 | "parser": "babel-eslint", 20 | "plugins": [ 21 | ], 22 | "rules": { 23 | "block-scoped-var": 2, 24 | "brace-style": [ 25 | 2, 26 | "1tbs", 27 | { 28 | "allowSingleLine": true 29 | } 30 | ], 31 | "camelcase": [ 32 | 2, 33 | { 34 | "properties": "always" 35 | } 36 | ], 37 | "comma-dangle": [ 38 | 2, 39 | "never" 40 | ], 41 | "comma-spacing": [ 42 | 2, 43 | { 44 | "before": false, 45 | "after": true 46 | } 47 | ], 48 | "comma-style": [ 49 | 2, 50 | "last" 51 | ], 52 | "complexity": 0, 53 | "consistent-return": 1, 54 | "consistent-this": 0, 55 | "curly": [ 56 | 2, 57 | "multi-line" 58 | ], 59 | "default-case": 0, 60 | "dot-location": [ 61 | 2, 62 | "property" 63 | ], 64 | "dot-notation": 0, 65 | "eol-last": 2, 66 | "eqeqeq": [ 67 | 2, 68 | "allow-null" 69 | ], 70 | "func-names": 0, 71 | "func-style": 0, 72 | "generator-star-spacing": [ 73 | 2, 74 | "both" 75 | ], 76 | "guard-for-in": 0, 77 | "handle-callback-err": [ 78 | 2, 79 | "^(err|error|anySpecificError)$" 80 | ], 81 | "indent": [ 82 | 2, 83 | 2, 84 | { 85 | "SwitchCase": 1 86 | } 87 | ], 88 | "key-spacing": [ 89 | 2, 90 | { 91 | "beforeColon": false, 92 | "afterColon": true 93 | } 94 | ], 95 | "keyword-spacing": [ 96 | 2, 97 | { 98 | "before": true, 99 | "after": true 100 | } 101 | ], 102 | "linebreak-style": 0, 103 | "max-depth": 0, 104 | "max-len": [ 105 | 2, 106 | 120, 107 | 4 108 | ], 109 | "max-nested-callbacks": 0, 110 | "max-params": 0, 111 | "max-statements": 0, 112 | "new-cap": [ 113 | 2, 114 | { 115 | "newIsCap": true, 116 | "capIsNew": false 117 | } 118 | ], 119 | "newline-after-var": [ 120 | 2, 121 | "always" 122 | ], 123 | "new-parens": 2, 124 | "no-alert": 0, 125 | "no-array-constructor": 2, 126 | "no-bitwise": 0, 127 | "no-caller": 2, 128 | "no-catch-shadow": 0, 129 | "no-cond-assign": 2, 130 | "no-console": 0, 131 | "no-constant-condition": 0, 132 | "no-continue": 0, 133 | "no-control-regex": 2, 134 | "no-debugger": 2, 135 | "no-delete-var": 2, 136 | "no-div-regex": 0, 137 | "no-dupe-args": 2, 138 | "no-dupe-keys": 2, 139 | "no-duplicate-case": 2, 140 | "no-else-return": 2, 141 | "no-empty": 0, 142 | "no-empty-character-class": 2, 143 | "no-eq-null": 0, 144 | "no-eval": 2, 145 | "no-ex-assign": 2, 146 | "no-extend-native": 2, 147 | "no-extra-bind": 2, 148 | "no-extra-boolean-cast": 2, 149 | "no-extra-parens": 0, 150 | "no-extra-semi": 0, 151 | "no-extra-strict": 0, 152 | "no-fallthrough": 2, 153 | "no-floating-decimal": 2, 154 | "no-func-assign": 2, 155 | "no-implied-eval": 2, 156 | "no-inline-comments": 0, 157 | "no-inner-declarations": [ 158 | 2, 159 | "functions" 160 | ], 161 | "no-invalid-regexp": 2, 162 | "no-irregular-whitespace": 2, 163 | "no-iterator": 2, 164 | "no-label-var": 2, 165 | "no-labels": 2, 166 | "no-lone-blocks": 0, 167 | "no-lonely-if": 0, 168 | "no-loop-func": 0, 169 | "no-mixed-requires": 0, 170 | "no-mixed-spaces-and-tabs": [ 171 | 2, 172 | false 173 | ], 174 | "no-multi-spaces": 2, 175 | "no-multi-str": 2, 176 | "no-multiple-empty-lines": [ 177 | 2, 178 | { 179 | "max": 1 180 | } 181 | ], 182 | "no-native-reassign": 2, 183 | "no-negated-in-lhs": 2, 184 | "no-nested-ternary": 0, 185 | "no-new": 2, 186 | "no-new-func": 2, 187 | "no-new-object": 2, 188 | "no-new-require": 2, 189 | "no-new-wrappers": 2, 190 | "no-obj-calls": 2, 191 | "no-octal": 2, 192 | "no-octal-escape": 2, 193 | "no-path-concat": 0, 194 | "no-plusplus": 0, 195 | "no-process-env": 0, 196 | "no-process-exit": 0, 197 | "no-proto": 2, 198 | "no-redeclare": 2, 199 | "no-regex-spaces": 2, 200 | "no-reserved-keys": 0, 201 | "no-restricted-modules": 0, 202 | "no-return-assign": 2, 203 | "no-script-url": 0, 204 | "no-self-compare": 2, 205 | "no-sequences": 2, 206 | "no-shadow": 0, 207 | "no-shadow-restricted-names": 2, 208 | "no-spaced-func": 2, 209 | "no-sparse-arrays": 2, 210 | "no-sync": 0, 211 | "no-ternary": 0, 212 | "no-throw-literal": 2, 213 | "no-trailing-spaces": 2, 214 | "no-undef": 2, 215 | "no-undef-init": 2, 216 | "no-undefined": 0, 217 | "no-underscore-dangle": 0, 218 | "no-unneeded-ternary": 2, 219 | "no-unreachable": 2, 220 | "no-unused-expressions": 0, 221 | "no-unused-vars": [ 222 | 1, 223 | { 224 | "vars": "all", 225 | "args": "none" 226 | } 227 | ], 228 | "no-use-before-define": 2, 229 | "no-var": 0, 230 | "no-void": 0, 231 | "no-warning-comments": 0, 232 | "no-with": 2, 233 | "one-var": 0, 234 | "operator-assignment": 0, 235 | "operator-linebreak": [ 236 | 2, 237 | "after" 238 | ], 239 | "padded-blocks": 0, 240 | "quote-props": 0, 241 | "quotes": [ 242 | 2, 243 | "single", 244 | "avoid-escape" 245 | ], 246 | "radix": 0, 247 | "semi": [ 248 | 2, 249 | "always" 250 | ], 251 | "semi-spacing": 0, 252 | "sort-vars": 0, 253 | "space-before-blocks": [ 254 | 2, 255 | "always" 256 | ], 257 | "space-before-function-paren": [ 258 | 2, 259 | { 260 | "anonymous": "always", 261 | "named": "never" 262 | } 263 | ], 264 | "space-in-brackets": 0, 265 | "space-in-parens": [ 266 | 2, 267 | "never" 268 | ], 269 | "space-infix-ops": 2, 270 | "space-unary-ops": [ 271 | 2, 272 | { 273 | "words": true, 274 | "nonwords": false 275 | } 276 | ], 277 | "spaced-comment": [ 278 | 2, 279 | "always" 280 | ], 281 | "strict": 0, 282 | "use-isnan": 2, 283 | "valid-jsdoc": 0, 284 | "valid-typeof": 2, 285 | "vars-on-top": 2, 286 | "wrap-iife": [ 287 | 2, 288 | "any" 289 | ], 290 | "wrap-regex": 0, 291 | "yoda": [ 292 | 2, 293 | "never" 294 | ] 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | 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. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | ## Scope 32 | 33 | 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. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team members through their social media sites or at _git @ itsjavi.com_. 38 | 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. 39 | 40 | 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. 41 | 42 | ## Attribution 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 45 | 46 | [homepage]: http://contributor-covenant.org 47 | [version]: http://contributor-covenant.org/version/1/4/ 48 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributions are welcome! 4 | 5 | ## Support 6 | 7 | If you get stuck with this library you can search or ask in the project 8 | [issues](https://github.com/itsjavi/bootstrap-colorpicker/issues) or in 9 | [StackOverflow](http://stackoverflow.com/). 10 | 11 | [Describe your problem or questions in detail](./ISSUE_TEMPLATE.md) and follow the steps mentioned in the "Issues" 12 | section of this document. 13 | 14 | 15 | ## Issues 16 | 17 | - Search for existing issues before creating a new one, to avoid duplication. 18 | - Give the issue a proper title. Use prefixes like `[feature request]`, `[suggestion]` or `[question]` 19 | in front of the title when it is not related to a possible bug. 20 | - When the issue is related to a possible bug, please always fill the [suggested template](./ISSUE_TEMPLATE.md) 21 | where applicable. 22 | - It is mandatory to provide live examples using [JSFiddle](https://jsfiddle.net/3paut4qn/1/) when applicable, 23 | that's the quickest and most efficient way for everyone to help you. 24 | 25 | 26 | ## Pull Requests 27 | 28 | Pull Requests fixing existing issues are really appreciated, but you can also add a new features. 29 | If your new feature works and receives the approval of the community, it will be merged. 30 | 31 | - Your working environment will need `node` v10.x with `npm`, `gulp` and `ava`. 32 | - After a fresh clone for your fork, you need to run `npm install` inside the project's root folder. 33 | - Before committing your changes to Github you have to assure that the tests are green and the build is successful. 34 | 35 | - Run the build process and make sure it doesn't produce any error: `npm run build`. 36 | - Run all tests and the linter: `npm run test`. 37 | - Check the documentation: `npm start` and go to [http://localhost:8080/](http://localhost:8080/). 38 | 39 | - Respect the coding style of the project, do not change it if not necessary. 40 | - Test your code changes at least in Chrome and Firefox. 41 | Preferably it should also be tested in mobile browsers and Edge. 42 | - If possible, cover all your changes with unit tests. 43 | - New features and new options should come with the corresponding documentation and demo. 44 | - When creating a new Pull Request of your branch to the original repository, 45 | please fill the [suggested template](./PULL_REQUEST_TEMPLATE.md) where applicable. 46 | 47 | ### Documentation updates 48 | 49 | To contribute with the documentation, you only need to update the JSDoc comments of the `src/js` code 50 | and the examples in the `src/hbs` folder. 51 | 52 | After that and after the documentation changes have been merged into master, the project maintainers 53 | can run `npm run publish-docs` to update the `gh-pages` branch and the documentation website. 54 | 55 | 56 | ### Code of Conduct 57 | Please respect the [Code of Conduct](./.github/CODE_OF_CONDUCT.md). 58 | 59 |
Colorpicker version | 40 |Compatible Bootstrap version | 41 |Dependencies | 42 |
---|---|---|
47 | v2.x 48 | Documentation 49 | |
50 | Bootstrap 3 or 4 | 51 |
52 |
|
57 |
60 | v3.x 61 | Documentation 62 | |
63 | Bootstrap 4 or without Bootstrap | 64 |
65 |
|
71 |
8 | The colorpicker ColorItem class uses and comes bundled with 9 | Qix Color, 10 | a great color parsing library with a friendly API that allows you to manipulate and convert the color. 11 | ColorItem is just an abstraction layer on top of the library, which is more 12 | convenient for Bootstrap Colorpicker. 13 |
14 | 15 |In this example we use the ColorItem API to add color swatches based on a tetrad of the selected color.
16 | {{/content}} 17 | {{#content "code"}} 18 |This example shows how to tweak the Colorpicker styles and settings to make it look bigger.
66 | {{/content}} 67 | {{#content "code"}} 68 | 69 | 105 | 127 | {{/content}} 128 | {{/extend}} 129 | 130 | {{#extend "example"}} 131 | {{#content "title"}} Customize the colorpicker template {{/content}} 132 | {{#content "description"}} 133 |This example shows how to use a custom template with a button inside.
134 | {{/content}} 135 | {{#content "code"}} 136 | 137 | 154 | {{/content}} 155 | {{/extend}} 156 | 157 | {{/content}} 158 | {{/extend}} 159 | -------------------------------------------------------------------------------- /src/docs/examples/p03_Colorpicker_Events.hbs: -------------------------------------------------------------------------------- 1 | {{#extend "example-group"}} 2 | {{#content "description"}} 3 |
4 | Using the different Bootstrap Colorpicker events is the best way to react to any colorpicker action.
5 | Check the full list of events.
6 |
7 | In all events, the first argument passed to the event handlers (the event) contains the colorpicker
,
8 | color
objects, as well as the value
, representing the color string.
9 |
18 | This example listens to the colorpickerChange
and colorpickerCreate
events and
19 | changes the background color of the parent element using the complementary color of the selected one.
20 | This also shows how to use the ColorItem object.
21 |
This example shows how to use a custom template with an input inside, that is able to set the color.
49 | {{/content}} 50 | {{#content "code"}} 51 | 52 | 93 | {{/content}} 94 | {{/extend}} 95 | 96 | 97 | {{/content}} 98 | {{/extend}} 99 | -------------------------------------------------------------------------------- /src/docs/examples/p04_Colorpicker_Extensions.hbs: -------------------------------------------------------------------------------- 1 | {{#extend "example-group"}} 2 | {{#content "description"}} 3 |
4 | To extend the functionality of Bootstrap Colorpicker, there is something called Bootstrap Colorpicker Extensions.
5 | The library comes with many core extensions like: preview (enabled by default),
6 | debugger, swatches and palette (used by swatches to resolve named colors), but
7 | you can also create your own using the
8 | $.colorpicker.Extension class and adding it globally to the
9 | Colorpicker.extensions
static property or to your instance using
10 | myColorpicker.registerExtension(MyExtensionClass, extensionOptions)
.
11 |
12 | Think of an extension as a plugin for Bootstrap Colorpicker, capable of listen and react to all of its events in
13 | a single place, as well as being able to resolve colors (like the swatches extension does).
14 |
23 | This example shows how use the swatches extension 24 | to define a list of preset color swatches (or color palette) 25 | and use their aliases as input values. W3C named colors are also supported. 26 |
27 | {{/content}} 28 | 29 | {{#content "code"}} 30 |
68 | When the debug option is enabled, the builtin debugger extension
69 | will be loaded.
70 |
71 | On debug mode, every event is logged (as debug/verbose level) in the browser console.
72 | You can also intercept those logs with the 'colorpickerDebug' event, as shown
73 | in this example.
74 |
75 | This example also shows how the events are triggered and when.
76 |
4 | Previous versions of the Bootstrap Colorpicker documentation and demos can be found here: 5 |
6 |Name | 58 | 59 | 60 |Type | 61 | 62 | 63 |Attributes | 64 | 65 | 66 | 67 |Default | 68 | 69 | 70 |Description | 71 |
---|---|---|---|---|
|
84 |
85 |
86 | 87 | 88 | 89 | 90 | | 91 | 92 | 93 |
94 |
95 | <optional> 96 | 97 | 98 | 99 | <nullable> 100 | 101 | 102 | 103 | <repeatable> 104 | 105 | |
106 |
107 |
108 |
109 | 110 | 111 | 112 | 113 | | 114 | 115 | 116 |117 | 118 | 119 | 120 | 121 | | 122 |
Name | 47 | 48 | 49 |Type | 50 | 51 | 52 |Attributes | 53 | 54 | 55 | 56 |Default | 57 | 58 | 59 |Description | 60 |
---|---|---|---|---|
|
73 |
74 |
75 | 76 | 77 | 78 | 79 | | 80 | 81 | 82 |
83 |
84 | <optional> 85 | 86 | 87 | 88 | <nullable> 89 | 90 | |
91 |
92 |
93 |
94 | 95 | 96 | 97 | 98 | | 99 | 100 | 101 |102 | |
7 |
6 | |
7 |
--------------------------------------------------------------------------------
/src/img/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsjavi/bootstrap-colorpicker/5b5f50b19eb7db556097fd3bc4c1c9718a6ef668/src/img/logo.ai
--------------------------------------------------------------------------------
/src/js/AddonHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Handles everything related to the colorpicker addon
5 | * @ignore
6 | */
7 | class AddonHandler {
8 | /**
9 | * @param {Colorpicker} colorpicker
10 | */
11 | constructor(colorpicker) {
12 | /**
13 | * @type {Colorpicker}
14 | */
15 | this.colorpicker = colorpicker;
16 | /**
17 | * @type {jQuery}
18 | */
19 | this.addon = null;
20 | }
21 |
22 | hasAddon() {
23 | return !!this.addon;
24 | }
25 |
26 | bind() {
27 | /**
28 | * @type {*|jQuery}
29 | */
30 | this.addon = this.colorpicker.options.addon ?
31 | this.colorpicker.element.find(this.colorpicker.options.addon) : null;
32 |
33 | if (this.addon && (this.addon.length === 0)) {
34 | // not found
35 | this.addon = null;
36 | }
37 | }
38 |
39 | unbind() {
40 | if (this.hasAddon()) {
41 | this.addon.off('.colorpicker');
42 | }
43 | }
44 |
45 | /**
46 | * If the addon element is present, its background color is updated
47 | */
48 | update() {
49 | if (!this.colorpicker.colorHandler.hasColor() || !this.hasAddon()) {
50 | return;
51 | }
52 |
53 | let colorStr = this.colorpicker.colorHandler.getColorString();
54 |
55 | let styles = {'background': colorStr};
56 |
57 | let icn = this.addon.find('i').eq(0);
58 |
59 | if (icn.length > 0) {
60 | icn.css(styles);
61 | } else {
62 | this.addon.css(styles);
63 | }
64 | }
65 | }
66 |
67 | export default AddonHandler;
68 |
--------------------------------------------------------------------------------
/src/js/ColorHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import $ from 'jquery';
4 | import ColorItem from './ColorItem';
5 |
6 | /**
7 | * Handles everything related to the colorpicker color
8 | * @ignore
9 | */
10 | class ColorHandler {
11 | /**
12 | * @param {Colorpicker} colorpicker
13 | */
14 | constructor(colorpicker) {
15 | /**
16 | * @type {Colorpicker}
17 | */
18 | this.colorpicker = colorpicker;
19 | }
20 |
21 | /**
22 | * @returns {*|String|ColorItem}
23 | */
24 | get fallback() {
25 | return this.colorpicker.options.fallbackColor ?
26 | this.colorpicker.options.fallbackColor : (this.hasColor() ? this.color : null);
27 | }
28 |
29 | /**
30 | * @returns {String|null}
31 | */
32 | get format() {
33 | if (this.colorpicker.options.format) {
34 | return this.colorpicker.options.format;
35 | }
36 |
37 | if (this.hasColor() && this.color.hasTransparency() && this.color.format.match(/^hex/)) {
38 | return this.isAlphaEnabled() ? 'rgba' : 'hex';
39 | }
40 |
41 | if (this.hasColor()) {
42 | return this.color.format;
43 | }
44 |
45 | return 'rgb';
46 | }
47 |
48 | /**
49 | * Internal color getter
50 | *
51 | * @type {ColorItem|null}
52 | */
53 | get color() {
54 | return this.colorpicker.element.data('color');
55 | }
56 |
57 | /**
58 | * Internal color setter
59 | *
60 | * @ignore
61 | * @param {ColorItem|null} value
62 | */
63 | set color(value) {
64 | this.colorpicker.element.data('color', value);
65 |
66 | if ((value instanceof ColorItem) && (this.colorpicker.options.format === 'auto')) {
67 | // If format is 'auto', use the first parsed one from now on
68 | this.colorpicker.options.format = this.color.format;
69 | }
70 | }
71 |
72 | bind() {
73 | // if the color option is set
74 | if (this.colorpicker.options.color) {
75 | this.color = this.createColor(this.colorpicker.options.color);
76 | return;
77 | }
78 |
79 | // if element[color] is empty and the input has a value
80 | if (!this.color && !!this.colorpicker.inputHandler.getValue()) {
81 | this.color = this.createColor(
82 | this.colorpicker.inputHandler.getValue(), this.colorpicker.options.autoInputFallback
83 | );
84 | }
85 | }
86 |
87 | unbind() {
88 | this.colorpicker.element.removeData('color');
89 | }
90 |
91 | /**
92 | * Returns the color string from the input value or the 'data-color' attribute of the input or element.
93 | * If empty, it returns the defaultValue parameter.
94 | *
95 | * @returns {String|*}
96 | */
97 | getColorString() {
98 | if (!this.hasColor()) {
99 | return '';
100 | }
101 |
102 | return this.color.string(this.format);
103 | }
104 |
105 | /**
106 | * Sets the color value
107 | *
108 | * @param {String|ColorItem} val
109 | */
110 | setColorString(val) {
111 | let color = val ? this.createColor(val) : null;
112 |
113 | this.color = color ? color : null;
114 | }
115 |
116 | /**
117 | * Creates a new color using the widget instance options (fallbackColor, format).
118 | *
119 | * @fires Colorpicker#colorpickerInvalid
120 | * @param {*} val
121 | * @param {boolean} fallbackOnInvalid
122 | * @param {boolean} autoHexInputFallback
123 | * @returns {ColorItem}
124 | */
125 | createColor(val, fallbackOnInvalid = true, autoHexInputFallback = false) {
126 | let disableHexInputFallback = !fallbackOnInvalid && !autoHexInputFallback;
127 |
128 | let color = new ColorItem(this.resolveColorDelegate(val), this.format, disableHexInputFallback);
129 |
130 | if (!color.isValid()) {
131 | if (fallbackOnInvalid) {
132 | color = this.getFallbackColor();
133 | }
134 |
135 | /**
136 | * (Colorpicker) Fired when the color is invalid and the fallback color is going to be used.
137 | *
138 | * @event Colorpicker#colorpickerInvalid
139 | */
140 | this.colorpicker.trigger('colorpickerInvalid', color, val);
141 | }
142 |
143 | if (!this.isAlphaEnabled()) {
144 | // Alpha is disabled
145 | color.alpha = 1;
146 | }
147 |
148 | return color;
149 | }
150 |
151 | getFallbackColor() {
152 | if (this.fallback && (this.fallback === this.color)) {
153 | return this.color;
154 | }
155 |
156 | let fallback = this.resolveColorDelegate(this.fallback);
157 |
158 | let color = new ColorItem(fallback, this.format);
159 |
160 | if (!color.isValid()) {
161 | console.warn('The fallback color is invalid. Falling back to the previous color or black if any.');
162 | return this.color ? this.color : new ColorItem('#000000', this.format);
163 | }
164 |
165 | return color;
166 | }
167 |
168 | /**
169 | * @returns {ColorItem}
170 | */
171 | assureColor() {
172 | if (!this.hasColor()) {
173 | this.color = this.getFallbackColor();
174 | }
175 |
176 | return this.color;
177 | }
178 |
179 | /**
180 | * Delegates the color resolution to the colorpicker extensions.
181 | *
182 | * @param {String|*} color
183 | * @param {boolean} realColor if true, the color should resolve into a real (not named) color code
184 | * @returns {ColorItem|String|*|null}
185 | */
186 | resolveColorDelegate(color, realColor = true) {
187 | let extResolvedColor = false;
188 |
189 | $.each(this.colorpicker.extensions, function (name, ext) {
190 | if (extResolvedColor !== false) {
191 | // skip if resolved
192 | return;
193 | }
194 | extResolvedColor = ext.resolveColor(color, realColor);
195 | });
196 |
197 | return extResolvedColor ? extResolvedColor : color;
198 | }
199 |
200 | /**
201 | * Checks if there is a color object, that it is valid and it is not a fallback
202 | * @returns {boolean}
203 | */
204 | isInvalidColor() {
205 | return !this.hasColor() || !this.color.isValid();
206 | }
207 |
208 | /**
209 | * Returns true if the useAlpha option is exactly true, false otherwise
210 | * @returns {boolean}
211 | */
212 | isAlphaEnabled() {
213 | return (this.colorpicker.options.useAlpha !== false);
214 | }
215 |
216 | /**
217 | * Returns true if the current color object is an instance of Color, false otherwise.
218 | * @returns {boolean}
219 | */
220 | hasColor() {
221 | return this.color instanceof ColorItem;
222 | }
223 | }
224 |
225 | export default ColorHandler;
226 |
--------------------------------------------------------------------------------
/src/js/Colorpicker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import Extension from './Extension';
4 | import defaults from './options';
5 | import coreExtensions from 'extensions';
6 | import $ from 'jquery';
7 | import SliderHandler from './SliderHandler';
8 | import PopupHandler from './PopupHandler';
9 | import InputHandler from './InputHandler';
10 | import ColorHandler from './ColorHandler';
11 | import PickerHandler from './PickerHandler';
12 | import AddonHandler from './AddonHandler';
13 | import ColorItem from './ColorItem';
14 |
15 | let colorPickerIdCounter = 0;
16 |
17 | let root = (typeof self !== 'undefined' ? self : this); // window
18 |
19 | /**
20 | * Colorpicker widget class
21 | */
22 | class Colorpicker {
23 | /**
24 | * Color class
25 | *
26 | * @static
27 | * @type {Color}
28 | */
29 | static get Color() {
30 | return ColorItem;
31 | }
32 |
33 | /**
34 | * Extension class
35 | *
36 | * @static
37 | * @type {Extension}
38 | */
39 | static get Extension() {
40 | return Extension;
41 | }
42 |
43 | /**
44 | * Internal color object
45 | *
46 | * @type {Color|null}
47 | */
48 | get color() {
49 | return this.colorHandler.color;
50 | }
51 |
52 | /**
53 | * Internal color format
54 | *
55 | * @type {String|null}
56 | */
57 | get format() {
58 | return this.colorHandler.format;
59 | }
60 |
61 | /**
62 | * Getter of the picker element
63 | *
64 | * @returns {jQuery|HTMLElement}
65 | */
66 | get picker() {
67 | return this.pickerHandler.picker;
68 | }
69 |
70 | /**
71 | * @fires Colorpicker#colorpickerCreate
72 | * @param {Object|String} element
73 | * @param {Object} options
74 | * @constructor
75 | */
76 | constructor(element, options) {
77 | colorPickerIdCounter += 1;
78 | /**
79 | * The colorpicker instance number
80 | * @type {number}
81 | */
82 | this.id = colorPickerIdCounter;
83 |
84 | /**
85 | * Latest colorpicker event
86 | *
87 | * @type {{name: String, e: *}}
88 | */
89 | this.lastEvent = {
90 | alias: null,
91 | e: null
92 | };
93 |
94 | /**
95 | * The element that the colorpicker is bound to
96 | *
97 | * @type {*|jQuery}
98 | */
99 | this.element = $(element)
100 | .addClass('colorpicker-element')
101 | .attr('data-colorpicker-id', this.id);
102 |
103 | /**
104 | * @type {defaults}
105 | */
106 | this.options = $.extend(true, {}, defaults, options, this.element.data());
107 |
108 | /**
109 | * @type {boolean}
110 | * @private
111 | */
112 | this.disabled = false;
113 |
114 | /**
115 | * Extensions added to this instance
116 | *
117 | * @type {Extension[]}
118 | */
119 | this.extensions = [];
120 |
121 | /**
122 | * The element where the
123 | * @type {*|jQuery}
124 | */
125 | this.container = (
126 | this.options.container === true ||
127 | (this.options.container !== true && this.options.inline === true)
128 | ) ? this.element : this.options.container;
129 |
130 | this.container = (this.container !== false) ? $(this.container) : false;
131 |
132 | /**
133 | * @type {InputHandler}
134 | */
135 | this.inputHandler = new InputHandler(this);
136 | /**
137 | * @type {ColorHandler}
138 | */
139 | this.colorHandler = new ColorHandler(this);
140 | /**
141 | * @type {SliderHandler}
142 | */
143 | this.sliderHandler = new SliderHandler(this);
144 | /**
145 | * @type {PopupHandler}
146 | */
147 | this.popupHandler = new PopupHandler(this, root);
148 | /**
149 | * @type {PickerHandler}
150 | */
151 | this.pickerHandler = new PickerHandler(this);
152 | /**
153 | * @type {AddonHandler}
154 | */
155 | this.addonHandler = new AddonHandler(this);
156 |
157 | this.init();
158 |
159 | // Emit a create event
160 | $($.proxy(function () {
161 | /**
162 | * (Colorpicker) When the Colorpicker instance has been created and the DOM is ready.
163 | *
164 | * @event Colorpicker#colorpickerCreate
165 | */
166 | this.trigger('colorpickerCreate');
167 | }, this));
168 | }
169 |
170 | /**
171 | * Initializes the plugin
172 | * @private
173 | */
174 | init() {
175 | // Init addon
176 | this.addonHandler.bind();
177 |
178 | // Init input
179 | this.inputHandler.bind();
180 |
181 | // Init extensions (before initializing the color)
182 | this.initExtensions();
183 |
184 | // Init color
185 | this.colorHandler.bind();
186 |
187 | // Init picker
188 | this.pickerHandler.bind();
189 |
190 | // Init sliders and popup
191 | this.sliderHandler.bind();
192 | this.popupHandler.bind();
193 |
194 | // Inject into the DOM (this may make it visible)
195 | this.pickerHandler.attach();
196 |
197 | // Update all components
198 | this.update();
199 |
200 | if (this.inputHandler.isDisabled()) {
201 | this.disable();
202 | }
203 | }
204 |
205 | /**
206 | * Initializes the plugin extensions
207 | * @private
208 | */
209 | initExtensions() {
210 | if (!Array.isArray(this.options.extensions)) {
211 | this.options.extensions = [];
212 | }
213 |
214 | if (this.options.debug) {
215 | this.options.extensions.push({name: 'debugger'});
216 | }
217 |
218 | // Register and instantiate extensions
219 | this.options.extensions.forEach((ext) => {
220 | this.registerExtension(Colorpicker.extensions[ext.name.toLowerCase()], ext.options || {});
221 | });
222 | }
223 |
224 | /**
225 | * Creates and registers the given extension
226 | *
227 | * @param {Extension} ExtensionClass The extension class to instantiate
228 | * @param {Object} [config] Extension configuration
229 | * @returns {Extension}
230 | */
231 | registerExtension(ExtensionClass, config = {}) {
232 | let ext = new ExtensionClass(this, config);
233 |
234 | this.extensions.push(ext);
235 | return ext;
236 | }
237 |
238 | /**
239 | * Destroys the current instance
240 | *
241 | * @fires Colorpicker#colorpickerDestroy
242 | */
243 | destroy() {
244 | let color = this.color;
245 |
246 | this.sliderHandler.unbind();
247 | this.inputHandler.unbind();
248 | this.popupHandler.unbind();
249 | this.colorHandler.unbind();
250 | this.addonHandler.unbind();
251 | this.pickerHandler.unbind();
252 |
253 | this.element
254 | .removeClass('colorpicker-element')
255 | .removeData('colorpicker')
256 | .removeData('color')
257 | .off('.colorpicker');
258 |
259 | /**
260 | * (Colorpicker) When the instance is destroyed with all events unbound.
261 | *
262 | * @event Colorpicker#colorpickerDestroy
263 | */
264 | this.trigger('colorpickerDestroy', color);
265 | }
266 |
267 | /**
268 | * Shows the colorpicker widget if hidden.
269 | * If the colorpicker is disabled this call will be ignored.
270 | *
271 | * @fires Colorpicker#colorpickerShow
272 | * @param {Event} [e]
273 | */
274 | show(e) {
275 | this.popupHandler.show(e);
276 | }
277 |
278 | /**
279 | * Hides the colorpicker widget.
280 | *
281 | * @fires Colorpicker#colorpickerHide
282 | * @param {Event} [e]
283 | */
284 | hide(e) {
285 | this.popupHandler.hide(e);
286 | }
287 |
288 | /**
289 | * Toggles the colorpicker between visible and hidden.
290 | *
291 | * @fires Colorpicker#colorpickerShow
292 | * @fires Colorpicker#colorpickerHide
293 | * @param {Event} [e]
294 | */
295 | toggle(e) {
296 | this.popupHandler.toggle(e);
297 | }
298 |
299 | /**
300 | * Returns the current color value as string
301 | *
302 | * @param {String|*} [defaultValue]
303 | * @returns {String|*}
304 | */
305 | getValue(defaultValue = null) {
306 | let val = this.colorHandler.color;
307 |
308 | val = (val instanceof ColorItem) ? val : defaultValue;
309 |
310 | if (val instanceof ColorItem) {
311 | return val.string(this.format);
312 | }
313 |
314 | return val;
315 | }
316 |
317 | /**
318 | * Sets the color manually
319 | *
320 | * @fires Colorpicker#colorpickerChange
321 | * @param {String|Color} val
322 | */
323 | setValue(val) {
324 | if (this.isDisabled()) {
325 | return;
326 | }
327 | let ch = this.colorHandler;
328 |
329 | if (
330 | (ch.hasColor() && !!val && ch.color.equals(val)) ||
331 | (!ch.hasColor() && !val)
332 | ) {
333 | // same color or still empty
334 | return;
335 | }
336 |
337 | ch.color = val ? ch.createColor(val, this.options.autoInputFallback, this.options.autoHexInputFallback) : null;
338 |
339 | /**
340 | * (Colorpicker) When the color is set programmatically with setValue().
341 | *
342 | * @event Colorpicker#colorpickerChange
343 | */
344 | this.trigger('colorpickerChange', ch.color, val);
345 |
346 | // force update if color has changed to empty
347 | this.update();
348 | }
349 |
350 | /**
351 | * Updates the UI and the input color according to the internal color.
352 | *
353 | * @fires Colorpicker#colorpickerUpdate
354 | */
355 | update() {
356 | if (this.colorHandler.hasColor()) {
357 | this.inputHandler.update();
358 | } else {
359 | this.colorHandler.assureColor();
360 | }
361 |
362 | this.addonHandler.update();
363 | this.pickerHandler.update();
364 |
365 | /**
366 | * (Colorpicker) Fired when the widget is updated.
367 | *
368 | * @event Colorpicker#colorpickerUpdate
369 | */
370 | this.trigger('colorpickerUpdate');
371 | }
372 |
373 | /**
374 | * Enables the widget and the input if any
375 | *
376 | * @fires Colorpicker#colorpickerEnable
377 | * @returns {boolean}
378 | */
379 | enable() {
380 | this.inputHandler.enable();
381 | this.disabled = false;
382 | this.picker.removeClass('colorpicker-disabled');
383 |
384 | /**
385 | * (Colorpicker) When the widget has been enabled.
386 | *
387 | * @event Colorpicker#colorpickerEnable
388 | */
389 | this.trigger('colorpickerEnable');
390 | return true;
391 | }
392 |
393 | /**
394 | * Disables the widget and the input if any
395 | *
396 | * @fires Colorpicker#colorpickerDisable
397 | * @returns {boolean}
398 | */
399 | disable() {
400 | this.inputHandler.disable();
401 | this.disabled = true;
402 | this.picker.addClass('colorpicker-disabled');
403 |
404 | /**
405 | * (Colorpicker) When the widget has been disabled.
406 | *
407 | * @event Colorpicker#colorpickerDisable
408 | */
409 | this.trigger('colorpickerDisable');
410 | return true;
411 | }
412 |
413 | /**
414 | * Returns true if this instance is enabled
415 | * @returns {boolean}
416 | */
417 | isEnabled() {
418 | return !this.isDisabled();
419 | }
420 |
421 | /**
422 | * Returns true if this instance is disabled
423 | * @returns {boolean}
424 | */
425 | isDisabled() {
426 | return this.disabled === true;
427 | }
428 |
429 | /**
430 | * Triggers a Colorpicker event.
431 | *
432 | * @param eventName
433 | * @param color
434 | * @param value
435 | */
436 | trigger(eventName, color = null, value = null) {
437 | this.element.trigger({
438 | type: eventName,
439 | colorpicker: this,
440 | color: color ? color : this.color,
441 | value: value ? value : this.getValue()
442 | });
443 | }
444 | }
445 |
446 | /**
447 | * Colorpicker extension classes, indexed by extension name
448 | *
449 | * @static
450 | * @type {Object} a map between the extension name and its class
451 | */
452 | Colorpicker.extensions = coreExtensions;
453 |
454 | export default Colorpicker;
455 |
--------------------------------------------------------------------------------
/src/js/Extension.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import $ from 'jquery';
4 |
5 | /**
6 | * Colorpicker extension class.
7 | */
8 | class Extension {
9 | /**
10 | * @param {Colorpicker} colorpicker
11 | * @param {Object} options
12 | */
13 | constructor(colorpicker, options = {}) {
14 | /**
15 | * The colorpicker instance
16 | * @type {Colorpicker}
17 | */
18 | this.colorpicker = colorpicker;
19 | /**
20 | * Extension options
21 | *
22 | * @type {Object}
23 | */
24 | this.options = options;
25 |
26 | if (!(this.colorpicker.element && this.colorpicker.element.length)) {
27 | throw new Error('Extension: this.colorpicker.element is not valid');
28 | }
29 |
30 | this.colorpicker.element.on('colorpickerCreate.colorpicker-ext', $.proxy(this.onCreate, this));
31 | this.colorpicker.element.on('colorpickerDestroy.colorpicker-ext', $.proxy(this.onDestroy, this));
32 | this.colorpicker.element.on('colorpickerUpdate.colorpicker-ext', $.proxy(this.onUpdate, this));
33 | this.colorpicker.element.on('colorpickerChange.colorpicker-ext', $.proxy(this.onChange, this));
34 | this.colorpicker.element.on('colorpickerInvalid.colorpicker-ext', $.proxy(this.onInvalid, this));
35 | this.colorpicker.element.on('colorpickerShow.colorpicker-ext', $.proxy(this.onShow, this));
36 | this.colorpicker.element.on('colorpickerHide.colorpicker-ext', $.proxy(this.onHide, this));
37 | this.colorpicker.element.on('colorpickerEnable.colorpicker-ext', $.proxy(this.onEnable, this));
38 | this.colorpicker.element.on('colorpickerDisable.colorpicker-ext', $.proxy(this.onDisable, this));
39 | }
40 |
41 | /**
42 | * Function called every time a new color needs to be created.
43 | * Return false to skip this resolver and continue with other extensions' ones
44 | * or return anything else to consider the color resolved.
45 | *
46 | * @param {ColorItem|String|*} color
47 | * @param {boolean} realColor if true, the color should resolve into a real (not named) color code
48 | * @return {ColorItem|String|*}
49 | */
50 | resolveColor(color, realColor = true) {
51 | return false;
52 | }
53 |
54 | /**
55 | * Method called after the colorpicker is created
56 | *
57 | * @listens Colorpicker#colorpickerCreate
58 | * @param {Event} event
59 | */
60 | onCreate(event) {
61 | // to be extended
62 | }
63 |
64 | /**
65 | * Method called after the colorpicker is destroyed
66 | *
67 | * @listens Colorpicker#colorpickerDestroy
68 | * @param {Event} event
69 | */
70 | onDestroy(event) {
71 | this.colorpicker.element.off('.colorpicker-ext');
72 | }
73 |
74 | /**
75 | * Method called after the colorpicker is updated
76 | *
77 | * @listens Colorpicker#colorpickerUpdate
78 | * @param {Event} event
79 | */
80 | onUpdate(event) {
81 | // to be extended
82 | }
83 |
84 | /**
85 | * Method called after the colorpicker color is changed
86 | *
87 | * @listens Colorpicker#colorpickerChange
88 | * @param {Event} event
89 | */
90 | onChange(event) {
91 | // to be extended
92 | }
93 |
94 | /**
95 | * Method called when the colorpicker color is invalid
96 | *
97 | * @listens Colorpicker#colorpickerInvalid
98 | * @param {Event} event
99 | */
100 | onInvalid(event) {
101 | // to be extended
102 | }
103 |
104 | /**
105 | * Method called after the colorpicker is hidden
106 | *
107 | * @listens Colorpicker#colorpickerHide
108 | * @param {Event} event
109 | */
110 | onHide(event) {
111 | // to be extended
112 | }
113 |
114 | /**
115 | * Method called after the colorpicker is shown
116 | *
117 | * @listens Colorpicker#colorpickerShow
118 | * @param {Event} event
119 | */
120 | onShow(event) {
121 | // to be extended
122 | }
123 |
124 | /**
125 | * Method called after the colorpicker is disabled
126 | *
127 | * @listens Colorpicker#colorpickerDisable
128 | * @param {Event} event
129 | */
130 | onDisable(event) {
131 | // to be extended
132 | }
133 |
134 | /**
135 | * Method called after the colorpicker is enabled
136 | *
137 | * @listens Colorpicker#colorpickerEnable
138 | * @param {Event} event
139 | */
140 | onEnable(event) {
141 | // to be extended
142 | }
143 | }
144 |
145 | export default Extension;
146 |
--------------------------------------------------------------------------------
/src/js/InputHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import $ from 'jquery';
4 | import ColorItem from './ColorItem';
5 |
6 | /**
7 | * Handles everything related to the colorpicker input
8 | * @ignore
9 | */
10 | class InputHandler {
11 | /**
12 | * @param {Colorpicker} colorpicker
13 | */
14 | constructor(colorpicker) {
15 | /**
16 | * @type {Colorpicker}
17 | */
18 | this.colorpicker = colorpicker;
19 | /**
20 | * @type {jQuery|false}
21 | */
22 | this.input = this.colorpicker.element.is('input') ? this.colorpicker.element : (this.colorpicker.options.input ?
23 | this.colorpicker.element.find(this.colorpicker.options.input) : false);
24 |
25 | if (this.input && (this.input.length === 0)) {
26 | this.input = false;
27 | }
28 |
29 | this._initValue();
30 | }
31 |
32 | bind() {
33 | if (!this.hasInput()) {
34 | return;
35 | }
36 | this.input.on({
37 | 'keyup.colorpicker': $.proxy(this.onkeyup, this)
38 | });
39 | this.input.on({
40 | 'change.colorpicker': $.proxy(this.onchange, this)
41 | });
42 | }
43 |
44 | unbind() {
45 | if (!this.hasInput()) {
46 | return;
47 | }
48 | this.input.off('.colorpicker');
49 | }
50 |
51 | _initValue() {
52 | if (!this.hasInput()) {
53 | return;
54 | }
55 |
56 | let val = '';
57 |
58 | [
59 | // candidates:
60 | this.input.val(),
61 | this.input.data('color'),
62 | this.input.attr('data-color')
63 | ].map((item) => {
64 | if (item && (val === '')) {
65 | val = item;
66 | }
67 | });
68 |
69 | if (val instanceof ColorItem) {
70 | val = this.getFormattedColor(val.string(this.colorpicker.format));
71 | } else if (!(typeof val === 'string' || val instanceof String)) {
72 | val = '';
73 | }
74 |
75 | this.input.prop('value', val);
76 | }
77 |
78 | /**
79 | * Returns the color string from the input value.
80 | * If there is no input the return value is false.
81 | *
82 | * @returns {String|boolean}
83 | */
84 | getValue() {
85 | if (!this.hasInput()) {
86 | return false;
87 | }
88 |
89 | return this.input.val();
90 | }
91 |
92 | /**
93 | * If the input element is present, it updates the value with the current color object color string.
94 | * If the value is changed, this method fires a "change" event on the input element.
95 | *
96 | * @param {String} val
97 | *
98 | * @fires Colorpicker#change
99 | */
100 | setValue(val) {
101 | if (!this.hasInput()) {
102 | return;
103 | }
104 |
105 | let inputVal = this.input.prop('value');
106 |
107 | val = val ? val : '';
108 |
109 | if (val === (inputVal ? inputVal : '')) {
110 | // No need to set value or trigger any event if nothing changed
111 | return;
112 | }
113 |
114 | this.input.prop('value', val);
115 |
116 | /**
117 | * (Input) Triggered on the input element when a new color is selected.
118 | *
119 | * @event Colorpicker#change
120 | */
121 | this.input.trigger({
122 | type: 'change',
123 | colorpicker: this.colorpicker,
124 | color: this.colorpicker.color,
125 | value: val
126 | });
127 | }
128 |
129 | /**
130 | * Returns the formatted color string, with the formatting options applied
131 | * (e.g. useHashPrefix)
132 | *
133 | * @param {String|null} val
134 | *
135 | * @returns {String}
136 | */
137 | getFormattedColor(val = null) {
138 | val = val ? val : this.colorpicker.colorHandler.getColorString();
139 |
140 | if (!val) {
141 | return '';
142 | }
143 |
144 | val = this.colorpicker.colorHandler.resolveColorDelegate(val, false);
145 |
146 | if (this.colorpicker.options.useHashPrefix === false) {
147 | val = val.replace(/^#/g, '');
148 | }
149 |
150 | return val;
151 | }
152 |
153 | /**
154 | * Returns true if the widget has an associated input element, false otherwise
155 | * @returns {boolean}
156 | */
157 | hasInput() {
158 | return (this.input !== false);
159 | }
160 |
161 | /**
162 | * Returns true if the input exists and is disabled
163 | * @returns {boolean}
164 | */
165 | isEnabled() {
166 | return this.hasInput() && !this.isDisabled();
167 | }
168 |
169 | /**
170 | * Returns true if the input exists and is disabled
171 | * @returns {boolean}
172 | */
173 | isDisabled() {
174 | return this.hasInput() && (this.input.prop('disabled') === true);
175 | }
176 |
177 | /**
178 | * Disables the input if any
179 | *
180 | * @fires Colorpicker#colorpickerDisable
181 | * @returns {boolean}
182 | */
183 | disable() {
184 | if (this.hasInput()) {
185 | this.input.prop('disabled', true);
186 | }
187 | }
188 |
189 | /**
190 | * Enables the input if any
191 | *
192 | * @fires Colorpicker#colorpickerEnable
193 | * @returns {boolean}
194 | */
195 | enable() {
196 | if (this.hasInput()) {
197 | this.input.prop('disabled', false);
198 | }
199 | }
200 |
201 | /**
202 | * Calls setValue with the current internal color value
203 | *
204 | * @fires Colorpicker#change
205 | */
206 | update() {
207 | if (!this.hasInput()) {
208 | return;
209 | }
210 |
211 | if (
212 | (this.colorpicker.options.autoInputFallback === false) &&
213 | this.colorpicker.colorHandler.isInvalidColor()
214 | ) {
215 | // prevent update if color is invalid, autoInputFallback is disabled and the last event is keyup.
216 | return;
217 | }
218 |
219 | this.setValue(this.getFormattedColor());
220 | }
221 |
222 | /**
223 | * Function triggered when the input has changed, so the colorpicker gets updated.
224 | *
225 | * @private
226 | * @param {Event} e
227 | * @returns {boolean}
228 | */
229 | onchange(e) {
230 | this.colorpicker.lastEvent.alias = 'input.change';
231 | this.colorpicker.lastEvent.e = e;
232 |
233 | let val = this.getValue();
234 |
235 | if (val !== e.value) {
236 | this.colorpicker.setValue(val);
237 | }
238 | }
239 |
240 | /**
241 | * Function triggered after a keyboard key has been released.
242 | *
243 | * @private
244 | * @param {Event} e
245 | * @returns {boolean}
246 | */
247 | onkeyup(e) {
248 | this.colorpicker.lastEvent.alias = 'input.keyup';
249 | this.colorpicker.lastEvent.e = e;
250 |
251 | let val = this.getValue();
252 |
253 | if (val !== e.value) {
254 | this.colorpicker.setValue(val);
255 | }
256 | }
257 | }
258 |
259 | export default InputHandler;
260 |
--------------------------------------------------------------------------------
/src/js/PickerHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import $ from 'jquery';
4 |
5 | /**
6 | * Handles everything related to the colorpicker UI
7 | * @ignore
8 | */
9 | class PickerHandler {
10 | /**
11 | * @param {Colorpicker} colorpicker
12 | */
13 | constructor(colorpicker) {
14 | /**
15 | * @type {Colorpicker}
16 | */
17 | this.colorpicker = colorpicker;
18 | /**
19 | * @type {jQuery}
20 | */
21 | this.picker = null;
22 | }
23 |
24 | get options() {
25 | return this.colorpicker.options;
26 | }
27 |
28 | get color() {
29 | return this.colorpicker.colorHandler.color;
30 | }
31 |
32 | bind() {
33 | /**
34 | * @type {jQuery|HTMLElement}
35 | */
36 | let picker = this.picker = $(this.options.template);
37 |
38 | if (this.options.customClass) {
39 | picker.addClass(this.options.customClass);
40 | }
41 |
42 | if (this.options.horizontal) {
43 | picker.addClass('colorpicker-horizontal');
44 | }
45 |
46 | if (this._supportsAlphaBar()) {
47 | this.options.useAlpha = true;
48 | picker.addClass('colorpicker-with-alpha');
49 | } else {
50 | this.options.useAlpha = false;
51 | }
52 | }
53 |
54 | attach() {
55 | // Inject the colorpicker element into the DOM
56 | let pickerParent = this.colorpicker.container ? this.colorpicker.container : null;
57 |
58 | if (pickerParent) {
59 | this.picker.appendTo(pickerParent);
60 | }
61 | }
62 |
63 | unbind() {
64 | this.picker.remove();
65 | }
66 |
67 | _supportsAlphaBar() {
68 | return (
69 | (this.options.useAlpha || (this.colorpicker.colorHandler.hasColor() && this.color.hasTransparency())) &&
70 | (this.options.useAlpha !== false) &&
71 | (!this.options.format || (this.options.format && !this.options.format.match(/^hex([36])?$/i)))
72 | );
73 | }
74 |
75 | /**
76 | * Changes the color adjustment bars using the current color object information.
77 | */
78 | update() {
79 | if (!this.colorpicker.colorHandler.hasColor()) {
80 | return;
81 | }
82 |
83 | let vertical = (this.options.horizontal !== true),
84 | slider = vertical ? this.options.sliders : this.options.slidersHorz;
85 |
86 | let saturationGuide = this.picker.find('.colorpicker-saturation .colorpicker-guide'),
87 | hueGuide = this.picker.find('.colorpicker-hue .colorpicker-guide'),
88 | alphaGuide = this.picker.find('.colorpicker-alpha .colorpicker-guide');
89 |
90 | let hsva = this.color.toHsvaRatio();
91 |
92 | // Set guides position
93 | if (hueGuide.length) {
94 | hueGuide.css(vertical ? 'top' : 'left', (vertical ? slider.hue.maxTop : slider.hue.maxLeft) * (1 - hsva.h));
95 | }
96 | if (alphaGuide.length) {
97 | alphaGuide.css(vertical ? 'top' : 'left', (vertical ? slider.alpha.maxTop : slider.alpha.maxLeft) * (1 - hsva.a));
98 | }
99 | if (saturationGuide.length) {
100 | saturationGuide.css({
101 | 'top': slider.saturation.maxTop - hsva.v * slider.saturation.maxTop,
102 | 'left': hsva.s * slider.saturation.maxLeft
103 | });
104 | }
105 |
106 | // Set saturation hue background
107 | this.picker.find('.colorpicker-saturation')
108 | .css('backgroundColor', this.color.getCloneHueOnly().toHexString()); // we only need hue
109 |
110 | // Set alpha color gradient
111 | let hexColor = this.color.toHexString();
112 |
113 | let alphaBg = '';
114 |
115 | if (this.options.horizontal) {
116 | alphaBg = `linear-gradient(to right, ${hexColor} 0%, transparent 100%)`;
117 | } else {
118 | alphaBg = `linear-gradient(to bottom, ${hexColor} 0%, transparent 100%)`;
119 | }
120 |
121 | this.picker.find('.colorpicker-alpha-color').css('background', alphaBg);
122 | }
123 | }
124 |
125 | export default PickerHandler;
126 |
--------------------------------------------------------------------------------
/src/js/PopupHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import $ from 'jquery';
4 | import _defaults from './options';
5 |
6 | /**
7 | * Handles everything related to the UI of the colorpicker popup: show, hide, position,...
8 | * @ignore
9 | */
10 | class PopupHandler {
11 | /**
12 | * @param {Colorpicker} colorpicker
13 | * @param {Window} root
14 | */
15 | constructor(colorpicker, root) {
16 | /**
17 | * @type {Window}
18 | */
19 | this.root = root;
20 | /**
21 | * @type {Colorpicker}
22 | */
23 | this.colorpicker = colorpicker;
24 | /**
25 | * @type {jQuery}
26 | */
27 | this.popoverTarget = null;
28 | /**
29 | * @type {jQuery}
30 | */
31 | this.popoverTip = null;
32 |
33 | /**
34 | * If true, the latest click was inside the popover
35 | * @type {boolean}
36 | */
37 | this.clicking = false;
38 | /**
39 | * @type {boolean}
40 | */
41 | this.hidding = false;
42 | /**
43 | * @type {boolean}
44 | */
45 | this.showing = false;
46 | }
47 |
48 | /**
49 | * @private
50 | * @returns {jQuery|false}
51 | */
52 | get input() {
53 | return this.colorpicker.inputHandler.input;
54 | }
55 |
56 | /**
57 | * @private
58 | * @returns {boolean}
59 | */
60 | get hasInput() {
61 | return this.colorpicker.inputHandler.hasInput();
62 | }
63 |
64 | /**
65 | * @private
66 | * @returns {jQuery|false}
67 | */
68 | get addon() {
69 | return this.colorpicker.addonHandler.addon;
70 | }
71 |
72 | /**
73 | * @private
74 | * @returns {boolean}
75 | */
76 | get hasAddon() {
77 | return this.colorpicker.addonHandler.hasAddon();
78 | }
79 |
80 | /**
81 | * @private
82 | * @returns {boolean}
83 | */
84 | get isPopover() {
85 | return !this.colorpicker.options.inline && !!this.popoverTip;
86 | }
87 |
88 | /**
89 | * Binds the different colorpicker elements to the focus/mouse/touch events so it reacts in order to show or
90 | * hide the colorpicker popup accordingly. It also adds the proper classes.
91 | */
92 | bind() {
93 | let cp = this.colorpicker;
94 |
95 | if (cp.options.inline) {
96 | cp.picker.addClass('colorpicker-inline colorpicker-visible');
97 | return; // no need to bind show/hide events for inline elements
98 | }
99 |
100 | cp.picker.addClass('colorpicker-popup colorpicker-hidden');
101 |
102 | // there is no input or addon
103 | if (!this.hasInput && !this.hasAddon) {
104 | return;
105 | }
106 |
107 | // create Bootstrap 4 popover
108 | if (cp.options.popover) {
109 | this.createPopover();
110 | }
111 |
112 | // bind addon show/hide events
113 | if (this.hasAddon) {
114 | // enable focus on addons
115 | if (!this.addon.attr('tabindex')) {
116 | this.addon.attr('tabindex', 0);
117 | }
118 |
119 | this.addon.on({
120 | 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.toggle, this)
121 | });
122 |
123 | this.addon.on({
124 | 'focus.colorpicker': $.proxy(this.show, this)
125 | });
126 |
127 | this.addon.on({
128 | 'focusout.colorpicker': $.proxy(this.hide, this)
129 | });
130 | }
131 |
132 | // bind input show/hide events
133 | if (this.hasInput && !this.hasAddon) {
134 | this.input.on({
135 | 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.show, this),
136 | 'focus.colorpicker': $.proxy(this.show, this)
137 | });
138 |
139 | this.input.on({
140 | 'focusout.colorpicker': $.proxy(this.hide, this)
141 | });
142 | }
143 |
144 | // reposition popup on window resize
145 | $(this.root).on('resize.colorpicker', $.proxy(this.reposition, this));
146 | }
147 |
148 | /**
149 | * Unbinds any event bound by this handler
150 | */
151 | unbind() {
152 | if (this.hasInput) {
153 | this.input.off({
154 | 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.show, this),
155 | 'focus.colorpicker': $.proxy(this.show, this)
156 | });
157 | this.input.off({
158 | 'focusout.colorpicker': $.proxy(this.hide, this)
159 | });
160 | }
161 |
162 | if (this.hasAddon) {
163 | this.addon.off({
164 | 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.toggle, this)
165 | });
166 | this.addon.off({
167 | 'focus.colorpicker': $.proxy(this.show, this)
168 | });
169 | this.addon.off({
170 | 'focusout.colorpicker': $.proxy(this.hide, this)
171 | });
172 | }
173 |
174 | if (this.popoverTarget) {
175 | this.popoverTarget.popover('dispose');
176 | }
177 |
178 | $(this.root).off('resize.colorpicker', $.proxy(this.reposition, this));
179 | $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.hide, this));
180 | $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.onClickingInside, this));
181 | }
182 |
183 | isClickingInside(e) {
184 | if (!e) {
185 | return false;
186 | }
187 |
188 | return (
189 | this.isOrIsInside(this.popoverTip, e.currentTarget) ||
190 | this.isOrIsInside(this.popoverTip, e.target) ||
191 | this.isOrIsInside(this.colorpicker.picker, e.currentTarget) ||
192 | this.isOrIsInside(this.colorpicker.picker, e.target)
193 | );
194 | }
195 |
196 | isOrIsInside(container, element) {
197 | if (!container || !element) {
198 | return false;
199 | }
200 |
201 | element = $(element);
202 |
203 | return (
204 | element.is(container) ||
205 | container.find(element).length > 0
206 | );
207 | }
208 |
209 | onClickingInside(e) {
210 | this.clicking = this.isClickingInside(e);
211 | }
212 |
213 | createPopover() {
214 | let cp = this.colorpicker;
215 |
216 | this.popoverTarget = this.hasAddon ? this.addon : this.input;
217 |
218 | cp.picker.addClass('colorpicker-bs-popover-content');
219 |
220 | this.popoverTarget.popover(
221 | $.extend(
222 | true,
223 | {},
224 | _defaults.popover,
225 | cp.options.popover,
226 | {trigger: 'manual', content: cp.picker, html: true}
227 | )
228 | );
229 |
230 | /* Bootstrap 5 added an official method to get the popover instance */
231 | /* global bootstrap */
232 | const useGetInstance = window.bootstrap &&
233 | window.bootstrap.Popover &&
234 | window.bootstrap.Popover.getInstance;
235 |
236 | this.popoverTip = useGetInstance ?
237 | $(bootstrap.Popover.getInstance(this.popoverTarget[0]).getTipElement()) :
238 | $(this.popoverTarget.popover('getTipElement').data('bs.popover').tip);
239 |
240 | this.popoverTip.addClass('colorpicker-bs-popover');
241 |
242 | this.popoverTarget.on('shown.bs.popover', $.proxy(this.fireShow, this));
243 | this.popoverTarget.on('hidden.bs.popover', $.proxy(this.fireHide, this));
244 | }
245 |
246 | /**
247 | * If the widget is not inside a container or inline, rearranges its position relative to its element offset.
248 | *
249 | * @param {Event} [e]
250 | * @private
251 | */
252 | reposition(e) {
253 | if (this.popoverTarget && this.isVisible()) {
254 | this.popoverTarget.popover('update');
255 | }
256 | }
257 |
258 | /**
259 | * Toggles the colorpicker between visible or hidden
260 | *
261 | * @fires Colorpicker#colorpickerShow
262 | * @fires Colorpicker#colorpickerHide
263 | * @param {Event} [e]
264 | */
265 | toggle(e) {
266 | if (this.isVisible()) {
267 | this.hide(e);
268 | } else {
269 | this.show(e);
270 | }
271 | }
272 |
273 | /**
274 | * Shows the colorpicker widget if hidden.
275 | *
276 | * @fires Colorpicker#colorpickerShow
277 | * @param {Event} [e]
278 | */
279 | show(e) {
280 | if (this.isVisible() || this.showing || this.hidding) {
281 | return;
282 | }
283 |
284 | this.showing = true;
285 | this.hidding = false;
286 | this.clicking = false;
287 |
288 | let cp = this.colorpicker;
289 |
290 | cp.lastEvent.alias = 'show';
291 | cp.lastEvent.e = e;
292 |
293 | // Prevent showing browser native HTML5 colorpicker
294 | if (
295 | (e && (!this.hasInput || this.input.attr('type') === 'color')) &&
296 | (e && e.preventDefault)
297 | ) {
298 | e.stopPropagation();
299 | e.preventDefault();
300 | }
301 |
302 | // If it's a popover, add event to the document to hide the picker when clicking outside of it
303 | if (this.isPopover) {
304 | $(this.root).on('resize.colorpicker', $.proxy(this.reposition, this));
305 | }
306 |
307 | // add visible class before popover is shown
308 | cp.picker.addClass('colorpicker-visible').removeClass('colorpicker-hidden');
309 |
310 | if (this.popoverTarget) {
311 | this.popoverTarget.popover('show');
312 | } else {
313 | this.fireShow();
314 | }
315 | }
316 |
317 | fireShow() {
318 | this.hidding = false;
319 | this.showing = false;
320 |
321 | if (this.isPopover) {
322 | // Add event to hide on outside click
323 | $(this.root.document).on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.hide, this));
324 | $(this.root.document).on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.onClickingInside, this));
325 | }
326 |
327 | /**
328 | * (Colorpicker) When show() is called and the widget can be shown.
329 | *
330 | * @event Colorpicker#colorpickerShow
331 | */
332 | this.colorpicker.trigger('colorpickerShow');
333 | }
334 |
335 | /**
336 | * Hides the colorpicker widget.
337 | * Hide is prevented when it is triggered by an event whose target element has been clicked/touched.
338 | *
339 | * @fires Colorpicker#colorpickerHide
340 | * @param {Event} [e]
341 | */
342 | hide(e) {
343 | if (this.isHidden() || this.showing || this.hidding) {
344 | return;
345 | }
346 |
347 | let cp = this.colorpicker, clicking = (this.clicking || this.isClickingInside(e));
348 |
349 | this.hidding = true;
350 | this.showing = false;
351 | this.clicking = false;
352 |
353 | cp.lastEvent.alias = 'hide';
354 | cp.lastEvent.e = e;
355 |
356 | // TODO: fix having to click twice outside when losing focus and last 2 clicks where inside the colorpicker
357 |
358 | // Prevent hide if triggered by an event and an element inside the colorpicker has been clicked/touched
359 | if (clicking) {
360 | this.hidding = false;
361 | return;
362 | }
363 |
364 | if (this.popoverTarget) {
365 | this.popoverTarget.popover('hide');
366 | } else {
367 | this.fireHide();
368 | }
369 | }
370 |
371 | fireHide() {
372 | this.hidding = false;
373 | this.showing = false;
374 |
375 | let cp = this.colorpicker;
376 |
377 | // add hidden class after popover is hidden
378 | cp.picker.addClass('colorpicker-hidden').removeClass('colorpicker-visible');
379 |
380 | // Unbind window and document events, since there is no need to keep them while the popup is hidden
381 | $(this.root).off('resize.colorpicker', $.proxy(this.reposition, this));
382 | $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.hide, this));
383 | $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.onClickingInside, this));
384 |
385 | /**
386 | * (Colorpicker) When hide() is called and the widget can be hidden.
387 | *
388 | * @event Colorpicker#colorpickerHide
389 | */
390 | cp.trigger('colorpickerHide');
391 | }
392 |
393 | focus() {
394 | if (this.hasAddon) {
395 | return this.addon.focus();
396 | }
397 | if (this.hasInput) {
398 | return this.input.focus();
399 | }
400 | return false;
401 | }
402 |
403 | /**
404 | * Returns true if the colorpicker element has the colorpicker-visible class and not the colorpicker-hidden one.
405 | * False otherwise.
406 | *
407 | * @returns {boolean}
408 | */
409 | isVisible() {
410 | return this.colorpicker.picker.hasClass('colorpicker-visible') &&
411 | !this.colorpicker.picker.hasClass('colorpicker-hidden');
412 | }
413 |
414 | /**
415 | * Returns true if the colorpicker element has the colorpicker-hidden class and not the colorpicker-visible one.
416 | * False otherwise.
417 | *
418 | * @returns {boolean}
419 | */
420 | isHidden() {
421 | return this.colorpicker.picker.hasClass('colorpicker-hidden') &&
422 | !this.colorpicker.picker.hasClass('colorpicker-visible');
423 | }
424 | }
425 |
426 | export default PopupHandler;
427 |
--------------------------------------------------------------------------------
/src/js/SliderHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import $ from 'jquery';
4 |
5 | /**
6 | * Class that handles all configured sliders on mouse or touch events.
7 | * @ignore
8 | */
9 | class SliderHandler {
10 | /**
11 | * @param {Colorpicker} colorpicker
12 | */
13 | constructor(colorpicker) {
14 | /**
15 | * @type {Colorpicker}
16 | */
17 | this.colorpicker = colorpicker;
18 | /**
19 | * @type {*|String}
20 | * @private
21 | */
22 | this.currentSlider = null;
23 | /**
24 | * @type {{left: number, top: number}}
25 | * @private
26 | */
27 | this.mousePointer = {
28 | left: 0,
29 | top: 0
30 | };
31 |
32 | /**
33 | * @type {Function}
34 | */
35 | this.onMove = $.proxy(this.defaultOnMove, this);
36 | }
37 |
38 | /**
39 | * This function is called every time a slider guide is moved
40 | * The scope of "this" is the SliderHandler object.
41 | *
42 | * @param {int} top
43 | * @param {int} left
44 | */
45 | defaultOnMove(top, left) {
46 | if (!this.currentSlider) {
47 | return;
48 | }
49 |
50 | let slider = this.currentSlider, cp = this.colorpicker, ch = cp.colorHandler;
51 |
52 | // Create a color object
53 | let color = !ch.hasColor() ? ch.getFallbackColor() : ch.color.getClone();
54 |
55 | // Adjust the guide position
56 | slider.guideStyle.left = left + 'px';
57 | slider.guideStyle.top = top + 'px';
58 |
59 | // Adjust the color
60 | if (slider.callLeft) {
61 | color[slider.callLeft](left / slider.maxLeft);
62 | }
63 | if (slider.callTop) {
64 | color[slider.callTop](top / slider.maxTop);
65 | }
66 |
67 | // Set the new color
68 | cp.setValue(color);
69 | cp.popupHandler.focus();
70 | }
71 |
72 | /**
73 | * Binds the colorpicker sliders to the mouse/touch events
74 | */
75 | bind() {
76 | let sliders = this.colorpicker.options.horizontal ? this.colorpicker
77 | .options.slidersHorz : this.colorpicker.options.sliders;
78 |
79 | let sliderClasses = [];
80 |
81 | for (let sliderName in sliders) {
82 | if (!sliders.hasOwnProperty(sliderName)) {
83 | continue;
84 | }
85 |
86 | sliderClasses.push(sliders[sliderName].selector);
87 | }
88 |
89 | this.colorpicker.picker.find(sliderClasses.join(', '))
90 | .on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.pressed, this));
91 | }
92 |
93 | /**
94 | * Unbinds any event bound by this handler
95 | */
96 | unbind() {
97 | $(this.colorpicker.picker).off({
98 | 'mousemove.colorpicker': $.proxy(this.moved, this),
99 | 'touchmove.colorpicker': $.proxy(this.moved, this),
100 | 'mouseup.colorpicker': $.proxy(this.released, this),
101 | 'touchend.colorpicker': $.proxy(this.released, this)
102 | });
103 | }
104 |
105 | /**
106 | * Function triggered when clicking in one of the color adjustment bars
107 | *
108 | * @private
109 | * @fires Colorpicker#mousemove
110 | * @param {Event} e
111 | */
112 | pressed(e) {
113 | if (this.colorpicker.isDisabled()) {
114 | return;
115 | }
116 | this.colorpicker.lastEvent.alias = 'pressed';
117 | this.colorpicker.lastEvent.e = e;
118 |
119 | if (!e.pageX && !e.pageY && e.originalEvent && e.originalEvent.touches) {
120 | e.pageX = e.originalEvent.touches[0].pageX;
121 | e.pageY = e.originalEvent.touches[0].pageY;
122 | }
123 | // e.stopPropagation();
124 | // e.preventDefault();
125 |
126 | let target = $(e.target);
127 |
128 | // detect the slider and set the limits and callbacks
129 | let zone = target.closest('div');
130 |
131 | let sliders = this.colorpicker.options.horizontal ? this.colorpicker
132 | .options.slidersHorz : this.colorpicker.options.sliders;
133 |
134 | if (zone.is('.colorpicker')) {
135 | return;
136 | }
137 |
138 | this.currentSlider = null;
139 |
140 | for (let sliderName in sliders) {
141 | if (!sliders.hasOwnProperty(sliderName)) {
142 | continue;
143 | }
144 |
145 | let slider = sliders[sliderName];
146 |
147 | if (zone.is(slider.selector)) {
148 | this.currentSlider = $.extend({}, slider, {name: sliderName});
149 | break;
150 | } else if (slider.childSelector !== undefined && zone.is(slider.childSelector)) {
151 | this.currentSlider = $.extend({}, slider, {name: sliderName});
152 | zone = zone.parent(); // zone.parents(slider.selector).first() ?
153 | break;
154 | }
155 | }
156 |
157 | let guide = zone.find('.colorpicker-guide').get(0);
158 |
159 | if (this.currentSlider === null || guide === null) {
160 | return;
161 | }
162 |
163 | let offset = zone.offset();
164 |
165 | // reference to guide's style
166 | this.currentSlider.guideStyle = guide.style;
167 | this.currentSlider.left = e.pageX - offset.left;
168 | this.currentSlider.top = e.pageY - offset.top;
169 | this.mousePointer = {
170 | left: e.pageX,
171 | top: e.pageY
172 | };
173 |
174 | // TODO: fix moving outside the picker makes the guides to keep moving. The event needs to be bound to the window.
175 | /**
176 | * (window.document) Triggered on mousedown for the document object,
177 | * so the color adjustment guide is moved to the clicked position.
178 | *
179 | * @event Colorpicker#mousemove
180 | */
181 | $(this.colorpicker.picker).on({
182 | 'mousemove.colorpicker': $.proxy(this.moved, this),
183 | 'touchmove.colorpicker': $.proxy(this.moved, this),
184 | 'mouseup.colorpicker': $.proxy(this.released, this),
185 | 'touchend.colorpicker': $.proxy(this.released, this)
186 | }).trigger('mousemove');
187 | }
188 |
189 | /**
190 | * Function triggered when dragging a guide inside one of the color adjustment bars.
191 | *
192 | * @private
193 | * @param {Event} e
194 | */
195 | moved(e) {
196 | this.colorpicker.lastEvent.alias = 'moved';
197 | this.colorpicker.lastEvent.e = e;
198 |
199 | if (!e.pageX && !e.pageY && e.originalEvent && e.originalEvent.touches) {
200 | e.pageX = e.originalEvent.touches[0].pageX;
201 | e.pageY = e.originalEvent.touches[0].pageY;
202 | }
203 |
204 | // e.stopPropagation();
205 | e.preventDefault(); // prevents scrolling on mobile
206 |
207 | let left = Math.max(
208 | 0,
209 | Math.min(
210 | this.currentSlider.maxLeft,
211 | this.currentSlider.left + ((e.pageX || this.mousePointer.left) - this.mousePointer.left)
212 | )
213 | );
214 |
215 | let top = Math.max(
216 | 0,
217 | Math.min(
218 | this.currentSlider.maxTop,
219 | this.currentSlider.top + ((e.pageY || this.mousePointer.top) - this.mousePointer.top)
220 | )
221 | );
222 |
223 | this.onMove(top, left);
224 | }
225 |
226 | /**
227 | * Function triggered when releasing the click in one of the color adjustment bars.
228 | *
229 | * @private
230 | * @param {Event} e
231 | */
232 | released(e) {
233 | this.colorpicker.lastEvent.alias = 'released';
234 | this.colorpicker.lastEvent.e = e;
235 |
236 | // e.stopPropagation();
237 | // e.preventDefault();
238 |
239 | $(this.colorpicker.picker).off({
240 | 'mousemove.colorpicker': this.moved,
241 | 'touchmove.colorpicker': this.moved,
242 | 'mouseup.colorpicker': this.released,
243 | 'touchend.colorpicker': this.released
244 | });
245 | }
246 | }
247 |
248 | export default SliderHandler;
249 |
--------------------------------------------------------------------------------
/src/js/extensions/Debugger.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import Extension from 'Extension';
4 | import $ from 'jquery';
5 |
6 | /**
7 | * Debugger extension class
8 | * @alias DebuggerExtension
9 | * @ignore
10 | */
11 | class Debugger extends Extension {
12 | constructor(colorpicker, options = {}) {
13 | super(colorpicker, options);
14 |
15 | /**
16 | * @type {number}
17 | */
18 | this.eventCounter = 0;
19 | if (this.colorpicker.inputHandler.hasInput()) {
20 | this.colorpicker.inputHandler.input.on('change.colorpicker-ext', $.proxy(this.onChangeInput, this));
21 | }
22 | }
23 |
24 | /**
25 | * @fires DebuggerExtension#colorpickerDebug
26 | * @param {string} eventName
27 | * @param {*} args
28 | */
29 | log(eventName, ...args) {
30 | this.eventCounter += 1;
31 |
32 | let logMessage = `#${this.eventCounter}: Colorpicker#${this.colorpicker.id} [${eventName}]`;
33 |
34 | console.debug(logMessage, ...args);
35 |
36 | /**
37 | * Whenever the debugger logs an event, this other event is emitted.
38 | *
39 | * @event DebuggerExtension#colorpickerDebug
40 | * @type {object} The event object
41 | * @property {Colorpicker} colorpicker The Colorpicker instance
42 | * @property {ColorItem} color The color instance
43 | * @property {{debugger: DebuggerExtension, eventName: String, logArgs: Array, logMessage: String}} debug
44 | * The debug info
45 | */
46 | this.colorpicker.element.trigger({
47 | type: 'colorpickerDebug',
48 | colorpicker: this.colorpicker,
49 | color: this.color,
50 | value: null,
51 | debug: {
52 | debugger: this,
53 | eventName: eventName,
54 | logArgs: args,
55 | logMessage: logMessage
56 | }
57 | });
58 | }
59 |
60 | resolveColor(color, realColor = true) {
61 | this.log('resolveColor()', color, realColor);
62 | return false;
63 | }
64 |
65 | onCreate(event) {
66 | this.log('colorpickerCreate');
67 | return super.onCreate(event);
68 | }
69 |
70 | onDestroy(event) {
71 | this.log('colorpickerDestroy');
72 | this.eventCounter = 0;
73 |
74 | if (this.colorpicker.inputHandler.hasInput()) {
75 | this.colorpicker.inputHandler.input.off('.colorpicker-ext');
76 | }
77 |
78 | return super.onDestroy(event);
79 | }
80 |
81 | onUpdate(event) {
82 | this.log('colorpickerUpdate');
83 | }
84 |
85 | /**
86 | * @listens Colorpicker#change
87 | * @param {Event} event
88 | */
89 | onChangeInput(event) {
90 | this.log('input:change.colorpicker', event.value, event.color);
91 | }
92 |
93 | onChange(event) {
94 | this.log('colorpickerChange', event.value, event.color);
95 | }
96 |
97 | onInvalid(event) {
98 | this.log('colorpickerInvalid', event.value, event.color);
99 | }
100 |
101 | onHide(event) {
102 | this.log('colorpickerHide');
103 | this.eventCounter = 0;
104 | }
105 |
106 | onShow(event) {
107 | this.log('colorpickerShow');
108 | }
109 |
110 | onDisable(event) {
111 | this.log('colorpickerDisable');
112 | }
113 |
114 | onEnable(event) {
115 | this.log('colorpickerEnable');
116 | }
117 | }
118 |
119 | export default Debugger;
120 |
--------------------------------------------------------------------------------
/src/js/extensions/Palette.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import Extension from 'Extension';
4 | import $ from 'jquery';
5 |
6 | let defaults = {
7 | /**
8 | * Key-value pairs defining a color alias and its CSS color representation.
9 | *
10 | * They can also be just an array of values. In that case, no special names are used, only the real colors.
11 | *
12 | * @type {Object|Array}
13 | * @default null
14 | * @example
15 | * {
16 | * 'black': '#000000',
17 | * 'white': '#ffffff',
18 | * 'red': '#FF0000',
19 | * 'default': '#777777',
20 | * 'primary': '#337ab7',
21 | * 'success': '#5cb85c',
22 | * 'info': '#5bc0de',
23 | * 'warning': '#f0ad4e',
24 | * 'danger': '#d9534f'
25 | * }
26 | *
27 | * @example ['#f0ad4e', '#337ab7', '#5cb85c']
28 | */
29 | colors: null,
30 | /**
31 | * If true, when a color swatch is selected the name (alias) will be used as input value,
32 | * otherwise the swatch real color value will be used.
33 | *
34 | * @type {boolean}
35 | * @default true
36 | */
37 | namesAsValues: true
38 | };
39 |
40 | /**
41 | * Palette extension
42 | * @ignore
43 | */
44 | class Palette extends Extension {
45 |
46 | /**
47 | * @returns {Object|Array}
48 | */
49 | get colors() {
50 | return this.options.colors;
51 | }
52 |
53 | constructor(colorpicker, options = {}) {
54 | super(colorpicker, $.extend(true, {}, defaults, options));
55 |
56 | if ((!Array.isArray(this.options.colors)) && (typeof this.options.colors !== 'object')) {
57 | this.options.colors = null;
58 | }
59 | }
60 |
61 | /**
62 | * @returns {int}
63 | */
64 | getLength() {
65 | if (!this.options.colors) {
66 | return 0;
67 | }
68 |
69 | if (Array.isArray(this.options.colors)) {
70 | return this.options.colors.length;
71 | }
72 |
73 | if (typeof this.options.colors === 'object') {
74 | return Object.keys(this.options.colors).length;
75 | }
76 |
77 | return 0;
78 | }
79 |
80 | resolveColor(color, realColor = true) {
81 | if (this.getLength() <= 0) {
82 | return false;
83 | }
84 |
85 | // Array of colors
86 | if (Array.isArray(this.options.colors)) {
87 | if (this.options.colors.indexOf(color) >= 0) {
88 | return color;
89 | }
90 | if (this.options.colors.indexOf(color.toUpperCase()) >= 0) {
91 | return color.toUpperCase();
92 | }
93 | if (this.options.colors.indexOf(color.toLowerCase()) >= 0) {
94 | return color.toLowerCase();
95 | }
96 | return false;
97 | }
98 |
99 | if (typeof this.options.colors !== 'object') {
100 | return false;
101 | }
102 |
103 | // Map of objects
104 | if (!this.options.namesAsValues || realColor) {
105 | return this.getValue(color, false);
106 | }
107 | return this.getName(color, this.getName('#' + color));
108 | }
109 |
110 | /**
111 | * Given a color value, returns the corresponding color name or defaultValue.
112 | *
113 | * @param {String} value
114 | * @param {*} defaultValue
115 | * @returns {*}
116 | */
117 | getName(value, defaultValue = false) {
118 | if (!(typeof value === 'string') || !this.options.colors) {
119 | return defaultValue;
120 | }
121 | for (let name in this.options.colors) {
122 | if (!this.options.colors.hasOwnProperty(name)) {
123 | continue;
124 | }
125 | if (this.options.colors[name].toLowerCase() === value.toLowerCase()) {
126 | return name;
127 | }
128 | }
129 | return defaultValue;
130 | }
131 |
132 | /**
133 | * Given a color name, returns the corresponding color value or defaultValue.
134 | *
135 | * @param {String} name
136 | * @param {*} defaultValue
137 | * @returns {*}
138 | */
139 | getValue(name, defaultValue = false) {
140 | if (!(typeof name === 'string') || !this.options.colors) {
141 | return defaultValue;
142 | }
143 | if (this.options.colors.hasOwnProperty(name)) {
144 | return this.options.colors[name];
145 | }
146 | return defaultValue;
147 | }
148 | }
149 |
150 | export default Palette;
151 |
--------------------------------------------------------------------------------
/src/js/extensions/Preview.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import Extension from 'Extension';
4 | import $ from 'jquery';
5 |
6 | /**
7 | * Color preview extension
8 | * @ignore
9 | */
10 | class Preview extends Extension {
11 | constructor(colorpicker, options = {}) {
12 | super(colorpicker, $.extend(true, {},
13 | {
14 | template: ' ',
15 | showText: true,
16 | format: colorpicker.format
17 | },
18 | options
19 | ));
20 |
21 | this.element = $(this.options.template);
22 | this.elementInner = this.element.find('div');
23 | }
24 |
25 | onCreate(event) {
26 | super.onCreate(event);
27 | this.colorpicker.picker.append(this.element);
28 | }
29 |
30 | onUpdate(event) {
31 | super.onUpdate(event);
32 |
33 | if (!event.color) {
34 | this.elementInner
35 | .css('backgroundColor', null)
36 | .css('color', null)
37 | .html('');
38 | return;
39 | }
40 |
41 | this.elementInner
42 | .css('backgroundColor', event.color.toRgbString());
43 |
44 | if (this.options.showText) {
45 | this.elementInner
46 | .html(event.color.string(this.options.format || this.colorpicker.format));
47 |
48 | if (event.color.isDark() && (event.color.alpha > 0.5)) {
49 | this.elementInner.css('color', 'white');
50 | } else {
51 | this.elementInner.css('color', 'black');
52 | }
53 | }
54 | }
55 | }
56 |
57 | export default Preview;
58 |
--------------------------------------------------------------------------------
/src/js/extensions/Swatches.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import Palette from './Palette';
4 | import $ from 'jquery';
5 |
6 | let defaults = {
7 | barTemplate: ` `,
10 | swatchTemplate: ''
11 | };
12 |
13 | /**
14 | * Color swatches extension
15 | * @ignore
16 | */
17 | class Swatches extends Palette {
18 | constructor(colorpicker, options = {}) {
19 | super(colorpicker, $.extend(true, {}, defaults, options));
20 | this.element = null;
21 | }
22 |
23 | isEnabled() {
24 | return this.getLength() > 0;
25 | }
26 |
27 | onCreate(event) {
28 | super.onCreate(event);
29 |
30 | if (!this.isEnabled()) {
31 | return;
32 | }
33 |
34 | this.element = $(this.options.barTemplate);
35 | this.load();
36 | this.colorpicker.picker.append(this.element);
37 | }
38 |
39 | load() {
40 | let colorpicker = this.colorpicker,
41 | swatchContainer = this.element.find('.colorpicker-swatches--inner'),
42 | isAliased = (this.options.namesAsValues === true) && !Array.isArray(this.colors);
43 |
44 | swatchContainer.empty();
45 |
46 | $.each(this.colors, (name, value) => {
47 | let $swatch = $(this.options.swatchTemplate)
48 | .attr('data-name', name)
49 | .attr('data-value', value)
50 | .attr('title', isAliased ? `${name}: ${value}` : value)
51 | .on('mousedown.colorpicker touchstart.colorpicker',
52 | function (e) {
53 | let $sw = $(this);
54 |
55 | // e.preventDefault();
56 |
57 | colorpicker.setValue(isAliased ? $sw.attr('data-name') : $sw.attr('data-value'));
58 | }
59 | );
60 |
61 | $swatch.find('.colorpicker-swatch--inner')
62 | .css('background-color', value);
63 |
64 | swatchContainer.append($swatch);
65 | });
66 |
67 | swatchContainer.append($(''));
68 | }
69 | }
70 |
71 | export default Swatches;
72 |
--------------------------------------------------------------------------------
/src/js/extensions/index.js:
--------------------------------------------------------------------------------
1 | import Debugger from './Debugger';
2 | import Preview from './Preview';
3 | import Swatches from './Swatches';
4 | import Palette from './Palette';
5 |
6 | export {
7 | Debugger, Preview, Swatches, Palette
8 | };
9 |
10 | export default {
11 | 'debugger': Debugger,
12 | 'preview': Preview,
13 | 'swatches': Swatches,
14 | 'palette': Palette
15 | };
16 |
--------------------------------------------------------------------------------
/src/js/options.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module
4 | */
5 |
6 | // adjust these values accordingly to the sass vars
7 | let sassVars = {
8 | 'bar_size_short': 16,
9 | 'base_margin': 6,
10 | 'columns': 6
11 | };
12 |
13 | let sliderSize = (sassVars.bar_size_short * sassVars.columns) + (sassVars.base_margin * (sassVars.columns - 1));
14 |
15 | /**
16 | * Colorpicker default options
17 | */
18 | export default {
19 | /**
20 | * Custom class to be added to the `.colorpicker-element` element
21 | *
22 | * @type {String|null}
23 | * @default null
24 | */
25 | customClass: null,
26 | /**
27 | * Sets a initial color, ignoring the one from the element/input value or the data-color attribute.
28 | *
29 | * @type {(String|ColorItem|boolean)}
30 | * @default false
31 | */
32 | color: false,
33 | /**
34 | * Fallback color to use when the given color is invalid.
35 | * If false, the latest valid color will be used as a fallback.
36 | *
37 | * @type {String|ColorItem|boolean}
38 | * @default false
39 | */
40 | fallbackColor: false,
41 | /**
42 | * Forces an specific color format. If 'auto', it will be automatically detected the first time only,
43 | * but if null it will be always recalculated.
44 | *
45 | * Note that the ending 'a' of the format meaning "alpha" has currently no effect, meaning that rgb is the same as
46 | * rgba excepting if the alpha channel is disabled (see useAlpha).
47 | *
48 | * @type {('rgb'|'hex'|'hsl'|'auto'|null)}
49 | * @default 'auto'
50 | */
51 | format: 'auto',
52 | /**
53 | * Horizontal mode layout.
54 | *
55 | * If true, the hue and alpha channel bars will be rendered horizontally, above the saturation selector.
56 | *
57 | * @type {boolean}
58 | * @default false
59 | */
60 | horizontal: false,
61 | /**
62 | * Forces to show the colorpicker as an inline element.
63 | *
64 | * Note that if there is no container specified, the inline element
65 | * will be added to the body, so you may want to set the container option.
66 | *
67 | * @type {boolean}
68 | * @default false
69 | */
70 | inline: false,
71 | /**
72 | * Container where the colorpicker is appended to in the DOM.
73 | *
74 | * If is a string (CSS selector), the colorpicker will be placed inside this container.
75 | * If true, the `.colorpicker-element` element itself will be used as the container.
76 | * If false, the document body is used as the container, unless it is a popover (in this case it is appended to the
77 | * popover body instead).
78 | *
79 | * @type {String|boolean}
80 | * @default false
81 | */
82 | container: false,
83 | /**
84 | * Bootstrap Popover options.
85 | * The trigger, content and html options are always ignored.
86 | *
87 | * @type {boolean}
88 | * @default Object
89 | */
90 | popover: {
91 | animation: true,
92 | placement: 'bottom',
93 | fallbackPlacement: 'flip'
94 | },
95 | /**
96 | * If true, loads the 'debugger' extension automatically, which logs the events in the console
97 | * @type {boolean}
98 | * @default false
99 | */
100 | debug: false,
101 | /**
102 | * Child CSS selector for the colorpicker input.
103 | *
104 | * @type {String}
105 | * @default 'input'
106 | */
107 | input: 'input',
108 | /**
109 | * Child CSS selector for the colorpicker addon.
110 | * If it exists, the child element background will be changed on color change.
111 | *
112 | * @type {String}
113 | * @default '.colorpicker-trigger, .colorpicker-input-addon'
114 | */
115 | addon: '.colorpicker-input-addon',
116 | /**
117 | * If true, the input content will be replaced always with a valid color,
118 | * if false, the invalid color will be left in the input,
119 | * while the internal color object will still resolve into a valid one.
120 | *
121 | * @type {boolean}
122 | * @default true
123 | */
124 | autoInputFallback: true,
125 | /**
126 | * If true, valid HEX3 colors will be converted to HEX6, even with
127 | * autoInputFallback set to false
128 | * if false, HEX3 colors will not be converted to HEX6, when autoInputFallback is false
129 | * (this has been an issue, when using HEX6 colors with
130 | * autoInputFallback set to false, HEX3 colors were
131 | * automatically converting to HEX6)
132 | *
133 | * @type {boolean}
134 | * @default false
135 | */
136 | autoHexInputFallback: true,
137 | /**
138 | * If true a hash will be prepended to hexadecimal colors.
139 | * If false, the hash will be removed.
140 | * This only affects the input values in hexadecimal format.
141 | *
142 | * @type {boolean}
143 | * @default true
144 | */
145 | useHashPrefix: true,
146 | /**
147 | * If true, the alpha channel bar will be displayed no matter what.
148 | *
149 | * If false, it will be always hidden and alpha channel will be disabled also programmatically, meaning that
150 | * the selected or typed color will be always opaque.
151 | *
152 | * If null, the alpha channel will be automatically disabled/enabled depending if the initial color format supports
153 | * alpha or not.
154 | *
155 | * @type {boolean}
156 | * @default true
157 | */
158 | useAlpha: true,
159 | /**
160 | * Colorpicker widget template
161 | * @type {String}
162 | * @example
163 | *
164 | *