df2profiler map drawer

a simple map drawer for df2profiler.com by clicking on the map to draw color on area

当前为 2024-03-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name df2profiler map drawer
  3. // @name:zh-TW df2profiler 地圖繪製器
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.4.0
  6. // @description a simple map drawer for df2profiler.com by clicking on the map to draw color on area
  7. // @description:zh-TW 透過點擊地圖來繪製顏色的簡易地圖繪製器
  8. // @author Archer_Wn
  9. // @match https://df2profiler.com/gamemap/
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=df2profiler.com
  11. // @grant none
  12. // @license GPL-3.0
  13. // ==/UserScript==
  14.  
  15. // Options
  16. let color = "rgba(255, 255, 0, 0.3)"; // default color for drawing
  17. const pvpColor = "rgba(255, 0, 0, 0.4)"; // color for pvp area
  18. const outpostColor = "rgba(0, 255, 0, 0.4)"; // color for outpost area
  19. const chunkSize = 6; // echo chunk have (chunkSize * chunkSize) cells
  20.  
  21. // Main
  22. let mouseDown = false;
  23. let drawingMode = false;
  24. let drewCells = [];
  25. function onPickerInput(value) {
  26. color = value;
  27. }
  28.  
  29. function onSavedMap() {
  30. const tbody = document.querySelector("#map tbody");
  31.  
  32. const backgroundImgUrl = window
  33. .getComputedStyle(document.querySelector("#map"))
  34. .getPropertyValue("background-image");
  35. const canvas = document.createElement("canvas");
  36. canvas.width = tbody.clientWidth;
  37. canvas.height = tbody.clientHeight;
  38. const ctx = canvas.getContext("2d");
  39. const img = new Image();
  40. img.src = backgroundImgUrl.slice(5, -2);
  41.  
  42. img.onload = () => {
  43. // draw background image
  44. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  45.  
  46. // draw pvp area
  47. const pvpCells = tbody.querySelectorAll(".pvpZone");
  48. pvpCells.forEach((cell) => {
  49. const rect = cell.getBoundingClientRect();
  50. const x = rect.left - tbody.getBoundingClientRect().left;
  51. const y = rect.top - tbody.getBoundingClientRect().top;
  52. ctx.fillStyle = pvpColor;
  53. ctx.fillRect(x, y, rect.width, rect.height);
  54. });
  55.  
  56. // draw outpost area
  57. const outpostCells = tbody.querySelectorAll(".outpost");
  58. outpostCells.forEach((cell) => {
  59. const rect = cell.getBoundingClientRect();
  60. const x = rect.left - tbody.getBoundingClientRect().left;
  61. const y = rect.top - tbody.getBoundingClientRect().top;
  62. ctx.fillStyle = outpostColor;
  63. ctx.fillRect(x, y, rect.width, rect.height);
  64. });
  65.  
  66. // draw area
  67. const cells = tbody.querySelectorAll("td");
  68. cells.forEach((cell) => {
  69. if (cell.style.backgroundColor === "") return;
  70. const rect = cell.getBoundingClientRect();
  71. const x = rect.left - tbody.getBoundingClientRect().left;
  72. const y = rect.top - tbody.getBoundingClientRect().top;
  73. ctx.fillStyle = cell.style.backgroundColor;
  74. ctx.fillRect(x, y, rect.width, rect.height);
  75. });
  76.  
  77. // draw grid (6x6 cells)
  78. const td = tbody.querySelector("td");
  79. const chunkWidth = td.clientWidth * chunkSize;
  80. const chunkHeight = td.clientHeight * chunkSize;
  81. ctx.strokeStyle = "white";
  82. ctx.lineWidth = 1;
  83. ctx.beginPath();
  84. for (let x = chunkWidth; x < canvas.width; x += chunkWidth) {
  85. ctx.moveTo(x, 0);
  86. ctx.lineTo(x, canvas.height);
  87. }
  88. for (let y = chunkHeight; y < canvas.height; y += chunkHeight) {
  89. ctx.moveTo(0, y);
  90. ctx.lineTo(canvas.width, y);
  91. }
  92. ctx.stroke();
  93.  
  94. // download image
  95. const a = document.createElement("a");
  96. a.href = canvas.toDataURL("image/png");
  97. a.download = "map.png";
  98. a.click();
  99. };
  100. }
  101.  
  102. (function () {
  103. "use strict";
  104.  
  105. const tbody = document.querySelector("#map tbody");
  106. tbody.addEventListener("click", (e) => {
  107. const cell = e.target.closest("td");
  108. if (!cell) return;
  109.  
  110. if (drawingMode) {
  111. cell.style.backgroundColor = color;
  112. } else {
  113. cell.style.backgroundColor = "";
  114. }
  115. });
  116. tbody.addEventListener("mousedown", (e) => {
  117. mouseDown = true;
  118. const cell = e.target.closest("td");
  119. if (!cell) return;
  120. if (cell.style.backgroundColor === "") {
  121. drawingMode = true;
  122. } else {
  123. drawingMode = false;
  124. }
  125. });
  126. tbody.addEventListener("mousemove", (e) => {
  127. if (!mouseDown) return;
  128. const cell = e.target.closest("td");
  129. if (!cell) return;
  130. if (drewCells.includes(cell)) return;
  131.  
  132. if (drawingMode) {
  133. cell.style.backgroundColor = color;
  134. } else {
  135. cell.style.backgroundColor = "";
  136. }
  137. drewCells.push(cell);
  138. });
  139. tbody.addEventListener("mouseup", (e) => {
  140. mouseDown = false;
  141. drewCells = [];
  142. });
  143.  
  144. const navbarLinks = document.querySelector("#navbar-links");
  145.  
  146. // color picker (rgba)
  147. const colorPicker = document.createElement("input");
  148. colorPicker.value = color;
  149. colorPicker.style.padding = "0 0.5rem";
  150. colorPicker.style.margin = "0.3rem 1rem";
  151. colorPicker.style.borderRadius =
  152. navbarLinks.getBoundingClientRect().height / 2 + "px";
  153. colorPicker.style.marginLeft = "auto";
  154. colorPicker.style.cursor = "pointer";
  155. colorPicker.setAttribute("data-jscolor", {});
  156. colorPicker.addEventListener("input", function () {
  157. onPickerInput(this.jscolor.toRGBAString());
  158. });
  159. navbarLinks.appendChild(colorPicker);
  160.  
  161. // screenshot button
  162. const screenshotBtn = document.createElement("button");
  163. screenshotBtn.textContent = "Screenshot Map";
  164. screenshotBtn.style.padding = "0 1rem";
  165. screenshotBtn.style.margin = "0.3rem 0rem";
  166. screenshotBtn.style.borderRadius =
  167. navbarLinks.getBoundingClientRect().height / 2 + "px";
  168. screenshotBtn.style.cursor = "pointer";
  169. navbarLinks.appendChild(screenshotBtn);
  170. screenshotBtn.addEventListener("click", onSavedMap);
  171. })();
  172.  
  173. /**
  174. * jscolor - JavaScript Color Picker
  175. *
  176. * @link http://jscolor.com
  177. * @license For open source use: GPLv3
  178. * For commercial use: JSColor Commercial License
  179. * @author Jan Odvarko - East Desire
  180. * @version 2.5.2
  181. *
  182. * See usage examples at http://jscolor.com/examples/
  183. */
  184.  
  185. (function (global, factory) {
  186. "use strict";
  187.  
  188. if (typeof module === "object" && typeof module.exports === "object") {
  189. // Export jscolor as a module
  190. module.exports = global.document
  191. ? factory(global)
  192. : function (win) {
  193. if (!win.document) {
  194. throw new Error("jscolor needs a window with document");
  195. }
  196. return factory(win);
  197. };
  198. return;
  199. }
  200.  
  201. // Default use (no module export)
  202. factory(global);
  203. })(typeof window !== "undefined" ? window : this, function (window) {
  204. // BEGIN factory
  205.  
  206. // BEGIN jscolor code
  207.  
  208. "use strict";
  209.  
  210. var jscolor = (function () {
  211. // BEGIN jscolor
  212.  
  213. var jsc = {
  214. initialized: false,
  215.  
  216. instances: [], // created instances of jscolor
  217.  
  218. readyQueue: [], // functions waiting to be called after init
  219.  
  220. register: function () {
  221. if (typeof window !== "undefined" && window.document) {
  222. if (window.document.readyState !== "loading") {
  223. jsc.pub.init();
  224. } else {
  225. window.document.addEventListener(
  226. "DOMContentLoaded",
  227. jsc.pub.init,
  228. false
  229. );
  230. }
  231. }
  232. },
  233.  
  234. installBySelector: function (selector, rootNode) {
  235. rootNode = rootNode ? jsc.node(rootNode) : window.document;
  236. if (!rootNode) {
  237. throw new Error("Missing root node");
  238. }
  239.  
  240. var elms = rootNode.querySelectorAll(selector);
  241.  
  242. // for backward compatibility with DEPRECATED installation/configuration using className
  243. var matchClass = new RegExp(
  244. "(^|\\s)(" + jsc.pub.lookupClass + ")(\\s*(\\{[^}]*\\})|\\s|$)",
  245. "i"
  246. );
  247.  
  248. for (var i = 0; i < elms.length; i += 1) {
  249. if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) {
  250. continue; // jscolor already installed on this element
  251. }
  252.  
  253. if (
  254. elms[i].type !== undefined &&
  255. elms[i].type.toLowerCase() == "color" &&
  256. jsc.isColorAttrSupported
  257. ) {
  258. continue; // skips inputs of type 'color' if supported by the browser
  259. }
  260.  
  261. var dataOpts, m;
  262.  
  263. if (
  264. (dataOpts = jsc.getDataAttr(elms[i], "jscolor")) !== null ||
  265. (elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED)
  266. ) {
  267. var targetElm = elms[i];
  268.  
  269. var optsStr = "";
  270. if (dataOpts !== null) {
  271. optsStr = dataOpts;
  272. } else if (m) {
  273. // installation using className (DEPRECATED)
  274. console.warn(
  275. 'Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' +
  276. jsc.docsRef
  277. );
  278. if (m[4]) {
  279. optsStr = m[4];
  280. }
  281. }
  282.  
  283. var opts = null;
  284. if (optsStr.trim()) {
  285. try {
  286. opts = jsc.parseOptionsStr(optsStr);
  287. } catch (e) {
  288. console.warn(e + "\n" + optsStr);
  289. }
  290. }
  291.  
  292. try {
  293. new jsc.pub(targetElm, opts);
  294. } catch (e) {
  295. console.warn(e);
  296. }
  297. }
  298. }
  299. },
  300.  
  301. parseOptionsStr: function (str) {
  302. var opts = null;
  303.  
  304. try {
  305. opts = JSON.parse(str);
  306. } catch (eParse) {
  307. if (!jsc.pub.looseJSON) {
  308. throw new Error(
  309. "Could not parse jscolor options as JSON: " + eParse
  310. );
  311. } else {
  312. // loose JSON syntax is enabled -> try to evaluate the options string as JavaScript object
  313. try {
  314. opts = new Function(
  315. "var opts = (" +
  316. str +
  317. '); return typeof opts === "object" ? opts : {};'
  318. )();
  319. } catch (eEval) {
  320. throw new Error("Could not evaluate jscolor options: " + eEval);
  321. }
  322. }
  323. }
  324. return opts;
  325. },
  326.  
  327. getInstances: function () {
  328. var inst = [];
  329. for (var i = 0; i < jsc.instances.length; i += 1) {
  330. // if the targetElement still exists, the instance is considered "alive"
  331. if (jsc.instances[i] && jsc.instances[i].targetElement) {
  332. inst.push(jsc.instances[i]);
  333. }
  334. }
  335. return inst;
  336. },
  337.  
  338. createEl: function (tagName) {
  339. var el = window.document.createElement(tagName);
  340. jsc.setData(el, "gui", true);
  341. return el;
  342. },
  343.  
  344. node: function (nodeOrSelector) {
  345. if (!nodeOrSelector) {
  346. return null;
  347. }
  348.  
  349. if (typeof nodeOrSelector === "string") {
  350. // query selector
  351. var sel = nodeOrSelector;
  352. var el = null;
  353. try {
  354. el = window.document.querySelector(sel);
  355. } catch (e) {
  356. console.warn(e);
  357. return null;
  358. }
  359. if (!el) {
  360. console.warn("No element matches the selector: %s", sel);
  361. }
  362. return el;
  363. }
  364.  
  365. if (jsc.isNode(nodeOrSelector)) {
  366. // DOM node
  367. return nodeOrSelector;
  368. }
  369.  
  370. console.warn(
  371. "Invalid node of type %s: %s",
  372. typeof nodeOrSelector,
  373. nodeOrSelector
  374. );
  375. return null;
  376. },
  377.  
  378. // See https://stackoverflow.com/questions/384286/
  379. isNode: function (val) {
  380. if (typeof Node === "object") {
  381. return val instanceof Node;
  382. }
  383. return (
  384. val &&
  385. typeof val === "object" &&
  386. typeof val.nodeType === "number" &&
  387. typeof val.nodeName === "string"
  388. );
  389. },
  390.  
  391. nodeName: function (node) {
  392. if (node && node.nodeName) {
  393. return node.nodeName.toLowerCase();
  394. }
  395. return false;
  396. },
  397.  
  398. removeChildren: function (node) {
  399. while (node.firstChild) {
  400. node.removeChild(node.firstChild);
  401. }
  402. },
  403.  
  404. isTextInput: function (el) {
  405. return (
  406. el && jsc.nodeName(el) === "input" && el.type.toLowerCase() === "text"
  407. );
  408. },
  409.  
  410. isButton: function (el) {
  411. if (!el) {
  412. return false;
  413. }
  414. var n = jsc.nodeName(el);
  415. return (
  416. n === "button" ||
  417. (n === "input" &&
  418. ["button", "submit", "reset"].indexOf(el.type.toLowerCase()) > -1)
  419. );
  420. },
  421.  
  422. isButtonEmpty: function (el) {
  423. switch (jsc.nodeName(el)) {
  424. case "input":
  425. return !el.value || el.value.trim() === "";
  426. case "button":
  427. return el.textContent.trim() === "";
  428. }
  429. return null; // could not determine element's text
  430. },
  431.  
  432. // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
  433. isPassiveEventSupported: (function () {
  434. var supported = false;
  435.  
  436. try {
  437. var opts = Object.defineProperty({}, "passive", {
  438. get: function () {
  439. supported = true;
  440. },
  441. });
  442. window.addEventListener("testPassive", null, opts);
  443. window.removeEventListener("testPassive", null, opts);
  444. } catch (e) {}
  445.  
  446. return supported;
  447. })(),
  448.  
  449. isColorAttrSupported: (function () {
  450. var elm = window.document.createElement("input");
  451. if (elm.setAttribute) {
  452. elm.setAttribute("type", "color");
  453. if (elm.type.toLowerCase() == "color") {
  454. return true;
  455. }
  456. }
  457. return false;
  458. })(),
  459.  
  460. dataProp: "_data_jscolor",
  461.  
  462. // usage:
  463. // setData(obj, prop, value)
  464. // setData(obj, {prop:value, ...})
  465. //
  466. setData: function () {
  467. var obj = arguments[0];
  468.  
  469. if (arguments.length === 3) {
  470. // setting a single property
  471. var data = obj.hasOwnProperty(jsc.dataProp)
  472. ? obj[jsc.dataProp]
  473. : (obj[jsc.dataProp] = {});
  474. var prop = arguments[1];
  475. var value = arguments[2];
  476.  
  477. data[prop] = value;
  478. return true;
  479. } else if (arguments.length === 2 && typeof arguments[1] === "object") {
  480. // setting multiple properties
  481. var data = obj.hasOwnProperty(jsc.dataProp)
  482. ? obj[jsc.dataProp]
  483. : (obj[jsc.dataProp] = {});
  484. var map = arguments[1];
  485.  
  486. for (var prop in map) {
  487. if (map.hasOwnProperty(prop)) {
  488. data[prop] = map[prop];
  489. }
  490. }
  491. return true;
  492. }
  493.  
  494. throw new Error("Invalid arguments");
  495. },
  496.  
  497. // usage:
  498. // removeData(obj, prop, [prop...])
  499. //
  500. removeData: function () {
  501. var obj = arguments[0];
  502. if (!obj.hasOwnProperty(jsc.dataProp)) {
  503. return true; // data object does not exist
  504. }
  505. for (var i = 1; i < arguments.length; i += 1) {
  506. var prop = arguments[i];
  507. delete obj[jsc.dataProp][prop];
  508. }
  509. return true;
  510. },
  511.  
  512. getData: function (obj, prop, setDefault) {
  513. if (!obj.hasOwnProperty(jsc.dataProp)) {
  514. // data object does not exist
  515. if (setDefault !== undefined) {
  516. obj[jsc.dataProp] = {}; // create data object
  517. } else {
  518. return undefined; // no value to return
  519. }
  520. }
  521. var data = obj[jsc.dataProp];
  522.  
  523. if (!data.hasOwnProperty(prop) && setDefault !== undefined) {
  524. data[prop] = setDefault;
  525. }
  526. return data[prop];
  527. },
  528.  
  529. getDataAttr: function (el, name) {
  530. var attrName = "data-" + name;
  531. var attrValue = el.getAttribute(attrName);
  532. return attrValue;
  533. },
  534.  
  535. setDataAttr: function (el, name, value) {
  536. var attrName = "data-" + name;
  537. el.setAttribute(attrName, value);
  538. },
  539.  
  540. _attachedGroupEvents: {},
  541.  
  542. attachGroupEvent: function (groupName, el, evnt, func) {
  543. if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
  544. jsc._attachedGroupEvents[groupName] = [];
  545. }
  546. jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
  547. el.addEventListener(evnt, func, false);
  548. },
  549.  
  550. detachGroupEvents: function (groupName) {
  551. if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
  552. for (
  553. var i = 0;
  554. i < jsc._attachedGroupEvents[groupName].length;
  555. i += 1
  556. ) {
  557. var evt = jsc._attachedGroupEvents[groupName][i];
  558. evt[0].removeEventListener(evt[1], evt[2], false);
  559. }
  560. delete jsc._attachedGroupEvents[groupName];
  561. }
  562. },
  563.  
  564. preventDefault: function (e) {
  565. if (e.preventDefault) {
  566. e.preventDefault();
  567. }
  568. e.returnValue = false;
  569. },
  570.  
  571. triggerEvent: function (el, eventName, bubbles, cancelable) {
  572. if (!el) {
  573. return;
  574. }
  575.  
  576. var ev = null;
  577.  
  578. if (typeof Event === "function") {
  579. ev = new Event(eventName, {
  580. bubbles: bubbles,
  581. cancelable: cancelable,
  582. });
  583. } else {
  584. // IE
  585. ev = window.document.createEvent("Event");
  586. ev.initEvent(eventName, bubbles, cancelable);
  587. }
  588.  
  589. if (!ev) {
  590. return false;
  591. }
  592.  
  593. // so that we know that the event was triggered internally
  594. jsc.setData(ev, "internal", true);
  595.  
  596. el.dispatchEvent(ev);
  597. return true;
  598. },
  599.  
  600. triggerInputEvent: function (el, eventName, bubbles, cancelable) {
  601. if (!el) {
  602. return;
  603. }
  604. if (jsc.isTextInput(el)) {
  605. jsc.triggerEvent(el, eventName, bubbles, cancelable);
  606. }
  607. },
  608.  
  609. eventKey: function (ev) {
  610. var keys = {
  611. 9: "Tab",
  612. 13: "Enter",
  613. 27: "Escape",
  614. };
  615. if (typeof ev.code === "string") {
  616. return ev.code;
  617. } else if (
  618. ev.keyCode !== undefined &&
  619. keys.hasOwnProperty(ev.keyCode)
  620. ) {
  621. return keys[ev.keyCode];
  622. }
  623. return null;
  624. },
  625.  
  626. strList: function (str) {
  627. if (!str) {
  628. return [];
  629. }
  630. return str.replace(/^\s+|\s+$/g, "").split(/\s+/);
  631. },
  632.  
  633. // The className parameter (str) can only contain a single class name
  634. hasClass: function (elm, className) {
  635. if (!className) {
  636. return false;
  637. }
  638. if (elm.classList !== undefined) {
  639. return elm.classList.contains(className);
  640. }
  641. // polyfill
  642. return (
  643. -1 !=
  644. (" " + elm.className.replace(/\s+/g, " ") + " ").indexOf(
  645. " " + className + " "
  646. )
  647. );
  648. },
  649.  
  650. // The className parameter (str) can contain multiple class names separated by whitespace
  651. addClass: function (elm, className) {
  652. var classNames = jsc.strList(className);
  653.  
  654. if (elm.classList !== undefined) {
  655. for (var i = 0; i < classNames.length; i += 1) {
  656. elm.classList.add(classNames[i]);
  657. }
  658. return;
  659. }
  660. // polyfill
  661. for (var i = 0; i < classNames.length; i += 1) {
  662. if (!jsc.hasClass(elm, classNames[i])) {
  663. elm.className += (elm.className ? " " : "") + classNames[i];
  664. }
  665. }
  666. },
  667.  
  668. // The className parameter (str) can contain multiple class names separated by whitespace
  669. removeClass: function (elm, className) {
  670. var classNames = jsc.strList(className);
  671.  
  672. if (elm.classList !== undefined) {
  673. for (var i = 0; i < classNames.length; i += 1) {
  674. elm.classList.remove(classNames[i]);
  675. }
  676. return;
  677. }
  678. // polyfill
  679. for (var i = 0; i < classNames.length; i += 1) {
  680. var repl = new RegExp(
  681. "^\\s*" +
  682. classNames[i] +
  683. "\\s*|" +
  684. "\\s*" +
  685. classNames[i] +
  686. "\\s*$|" +
  687. "\\s+" +
  688. classNames[i] +
  689. "(\\s+)",
  690. "g"
  691. );
  692. elm.className = elm.className.replace(repl, "$1");
  693. }
  694. },
  695.  
  696. getCompStyle: function (elm) {
  697. var compStyle = window.getComputedStyle
  698. ? window.getComputedStyle(elm)
  699. : elm.currentStyle;
  700.  
  701. // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
  702. // that's why we need to check if the returned value is non-empty
  703. if (!compStyle) {
  704. return {};
  705. }
  706. return compStyle;
  707. },
  708.  
  709. // Note:
  710. // Setting a property to NULL reverts it to the state before it was first set
  711. // with the 'reversible' flag enabled
  712. //
  713. setStyle: function (elm, styles, important, reversible) {
  714. // using '' for standard priority (IE10 apparently doesn't like value undefined)
  715. var priority = important ? "important" : "";
  716. var origStyle = null;
  717.  
  718. for (var prop in styles) {
  719. if (styles.hasOwnProperty(prop)) {
  720. var setVal = null;
  721.  
  722. if (styles[prop] === null) {
  723. // reverting a property value
  724.  
  725. if (!origStyle) {
  726. // get the original style object, but dont't try to create it if it doesn't exist
  727. origStyle = jsc.getData(elm, "origStyle");
  728. }
  729. if (origStyle && origStyle.hasOwnProperty(prop)) {
  730. // we have property's original value -> use it
  731. setVal = origStyle[prop];
  732. }
  733. } else {
  734. // setting a property value
  735.  
  736. if (reversible) {
  737. if (!origStyle) {
  738. // get the original style object and if it doesn't exist, create it
  739. origStyle = jsc.getData(elm, "origStyle", {});
  740. }
  741. if (!origStyle.hasOwnProperty(prop)) {
  742. // original property value not yet stored -> store it
  743. origStyle[prop] = elm.style[prop];
  744. }
  745. }
  746. setVal = styles[prop];
  747. }
  748.  
  749. if (setVal !== null) {
  750. elm.style.setProperty(prop, setVal, priority);
  751. }
  752. }
  753. }
  754. },
  755.  
  756. appendCss: function (css) {
  757. var head = document.querySelector("head");
  758. var style = document.createElement("style");
  759. style.innerText = css;
  760. head.appendChild(style);
  761. },
  762.  
  763. appendDefaultCss: function (css) {
  764. jsc.appendCss(
  765. [
  766. ".jscolor-wrap, .jscolor-wrap div, .jscolor-wrap canvas { " +
  767. "position:static; display:block; visibility:visible; overflow:visible; margin:0; padding:0; " +
  768. "border:none; border-radius:0; outline:none; z-index:auto; float:none; " +
  769. "width:auto; height:auto; left:auto; right:auto; top:auto; bottom:auto; min-width:0; min-height:0; max-width:none; max-height:none; " +
  770. "background:none; clip:auto; opacity:1; transform:none; box-shadow:none; box-sizing:content-box; " +
  771. "}",
  772. ".jscolor-wrap { clear:both; }",
  773. ".jscolor-wrap .jscolor-picker { position:relative; }",
  774. ".jscolor-wrap .jscolor-shadow { position:absolute; left:0; top:0; width:100%; height:100%; }",
  775. ".jscolor-wrap .jscolor-border { position:relative; }",
  776. ".jscolor-wrap .jscolor-palette { position:absolute; }",
  777. ".jscolor-wrap .jscolor-palette-sw { position:absolute; display:block; cursor:pointer; }",
  778. ".jscolor-wrap .jscolor-btn { position:absolute; overflow:hidden; white-space:nowrap; font:13px sans-serif; text-align:center; cursor:pointer; }",
  779. ].join("\n")
  780. );
  781. },
  782.  
  783. hexColor: function (r, g, b) {
  784. return (
  785. "#" +
  786. (
  787. ("0" + Math.round(r).toString(16)).slice(-2) +
  788. ("0" + Math.round(g).toString(16)).slice(-2) +
  789. ("0" + Math.round(b).toString(16)).slice(-2)
  790. ).toUpperCase()
  791. );
  792. },
  793.  
  794. hexaColor: function (r, g, b, a) {
  795. return (
  796. "#" +
  797. (
  798. ("0" + Math.round(r).toString(16)).slice(-2) +
  799. ("0" + Math.round(g).toString(16)).slice(-2) +
  800. ("0" + Math.round(b).toString(16)).slice(-2) +
  801. ("0" + Math.round(a * 255).toString(16)).slice(-2)
  802. ).toUpperCase()
  803. );
  804. },
  805.  
  806. rgbColor: function (r, g, b) {
  807. return (
  808. "rgb(" +
  809. Math.round(r) +
  810. "," +
  811. Math.round(g) +
  812. "," +
  813. Math.round(b) +
  814. ")"
  815. );
  816. },
  817.  
  818. rgbaColor: function (r, g, b, a) {
  819. return (
  820. "rgba(" +
  821. Math.round(r) +
  822. "," +
  823. Math.round(g) +
  824. "," +
  825. Math.round(b) +
  826. "," +
  827. Math.round((a === undefined || a === null ? 1 : a) * 100) / 100 +
  828. ")"
  829. );
  830. },
  831.  
  832. linearGradient: (function () {
  833. function getFuncName() {
  834. var stdName = "linear-gradient";
  835. var prefixes = ["", "-webkit-", "-moz-", "-o-", "-ms-"];
  836. var helper = window.document.createElement("div");
  837.  
  838. for (var i = 0; i < prefixes.length; i += 1) {
  839. var tryFunc = prefixes[i] + stdName;
  840. var tryVal = tryFunc + "(to right, rgba(0,0,0,0), rgba(0,0,0,0))";
  841.  
  842. helper.style.background = tryVal;
  843. if (helper.style.background) {
  844. // CSS background successfully set -> function name is supported
  845. return tryFunc;
  846. }
  847. }
  848. return stdName; // fallback to standard 'linear-gradient' without vendor prefix
  849. }
  850.  
  851. var funcName = getFuncName();
  852.  
  853. return function () {
  854. return (
  855. funcName + "(" + Array.prototype.join.call(arguments, ", ") + ")"
  856. );
  857. };
  858. })(),
  859.  
  860. setBorderRadius: function (elm, value) {
  861. jsc.setStyle(elm, { "border-radius": value || "0" });
  862. },
  863.  
  864. setBoxShadow: function (elm, value) {
  865. jsc.setStyle(elm, { "box-shadow": value || "none" });
  866. },
  867.  
  868. getElementPos: function (e, relativeToViewport) {
  869. var x = 0,
  870. y = 0;
  871. var rect = e.getBoundingClientRect();
  872. x = rect.left;
  873. y = rect.top;
  874. if (!relativeToViewport) {
  875. var viewPos = jsc.getViewPos();
  876. x += viewPos[0];
  877. y += viewPos[1];
  878. }
  879. return [x, y];
  880. },
  881.  
  882. getElementSize: function (e) {
  883. return [e.offsetWidth, e.offsetHeight];
  884. },
  885.  
  886. // get pointer's X/Y coordinates relative to viewport
  887. getAbsPointerPos: function (e) {
  888. var x = 0,
  889. y = 0;
  890. if (
  891. typeof e.changedTouches !== "undefined" &&
  892. e.changedTouches.length
  893. ) {
  894. // touch devices
  895. x = e.changedTouches[0].clientX;
  896. y = e.changedTouches[0].clientY;
  897. } else if (typeof e.clientX === "number") {
  898. x = e.clientX;
  899. y = e.clientY;
  900. }
  901. return { x: x, y: y };
  902. },
  903.  
  904. // get pointer's X/Y coordinates relative to target element
  905. getRelPointerPos: function (e) {
  906. var target = e.target || e.srcElement;
  907. var targetRect = target.getBoundingClientRect();
  908.  
  909. var x = 0,
  910. y = 0;
  911.  
  912. var clientX = 0,
  913. clientY = 0;
  914. if (
  915. typeof e.changedTouches !== "undefined" &&
  916. e.changedTouches.length
  917. ) {
  918. // touch devices
  919. clientX = e.changedTouches[0].clientX;
  920. clientY = e.changedTouches[0].clientY;
  921. } else if (typeof e.clientX === "number") {
  922. clientX = e.clientX;
  923. clientY = e.clientY;
  924. }
  925.  
  926. x = clientX - targetRect.left;
  927. y = clientY - targetRect.top;
  928. return { x: x, y: y };
  929. },
  930.  
  931. getViewPos: function () {
  932. var doc = window.document.documentElement;
  933. return [
  934. (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
  935. (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0),
  936. ];
  937. },
  938.  
  939. getViewSize: function () {
  940. var doc = window.document.documentElement;
  941. return [
  942. window.innerWidth || doc.clientWidth,
  943. window.innerHeight || doc.clientHeight,
  944. ];
  945. },
  946.  
  947. // r: 0-255
  948. // g: 0-255
  949. // b: 0-255
  950. //
  951. // returns: [ 0-360, 0-100, 0-100 ]
  952. //
  953. RGB_HSV: function (r, g, b) {
  954. r /= 255;
  955. g /= 255;
  956. b /= 255;
  957. var n = Math.min(Math.min(r, g), b);
  958. var v = Math.max(Math.max(r, g), b);
  959. var m = v - n;
  960. if (m === 0) {
  961. return [null, 0, 100 * v];
  962. }
  963. var h =
  964. r === n
  965. ? 3 + (b - g) / m
  966. : g === n
  967. ? 5 + (r - b) / m
  968. : 1 + (g - r) / m;
  969. return [60 * (h === 6 ? 0 : h), 100 * (m / v), 100 * v];
  970. },
  971.  
  972. // h: 0-360
  973. // s: 0-100
  974. // v: 0-100
  975. //
  976. // returns: [ 0-255, 0-255, 0-255 ]
  977. //
  978. HSV_RGB: function (h, s, v) {
  979. var u = 255 * (v / 100);
  980.  
  981. if (h === null) {
  982. return [u, u, u];
  983. }
  984.  
  985. h /= 60;
  986. s /= 100;
  987.  
  988. var i = Math.floor(h);
  989. var f = i % 2 ? h - i : 1 - (h - i);
  990. var m = u * (1 - s);
  991. var n = u * (1 - s * f);
  992. switch (i) {
  993. case 6:
  994. case 0:
  995. return [u, n, m];
  996. case 1:
  997. return [n, u, m];
  998. case 2:
  999. return [m, u, n];
  1000. case 3:
  1001. return [m, n, u];
  1002. case 4:
  1003. return [n, m, u];
  1004. case 5:
  1005. return [u, m, n];
  1006. }
  1007. },
  1008.  
  1009. parseColorString: function (str) {
  1010. var ret = {
  1011. rgba: null,
  1012. format: null, // 'hex' | 'hexa' | 'rgb' | 'rgba'
  1013. };
  1014.  
  1015. var m;
  1016.  
  1017. if ((m = str.match(/^\W*([0-9A-F]{3,8})\W*$/i))) {
  1018. // HEX notation
  1019.  
  1020. if (m[1].length === 8) {
  1021. // 8-char notation (= with alpha)
  1022. ret.format = "hexa";
  1023. ret.rgba = [
  1024. parseInt(m[1].slice(0, 2), 16),
  1025. parseInt(m[1].slice(2, 4), 16),
  1026. parseInt(m[1].slice(4, 6), 16),
  1027. parseInt(m[1].slice(6, 8), 16) / 255,
  1028. ];
  1029. } else if (m[1].length === 6) {
  1030. // 6-char notation
  1031. ret.format = "hex";
  1032. ret.rgba = [
  1033. parseInt(m[1].slice(0, 2), 16),
  1034. parseInt(m[1].slice(2, 4), 16),
  1035. parseInt(m[1].slice(4, 6), 16),
  1036. null,
  1037. ];
  1038. } else if (m[1].length === 3) {
  1039. // 3-char notation
  1040. ret.format = "hex";
  1041. ret.rgba = [
  1042. parseInt(m[1].charAt(0) + m[1].charAt(0), 16),
  1043. parseInt(m[1].charAt(1) + m[1].charAt(1), 16),
  1044. parseInt(m[1].charAt(2) + m[1].charAt(2), 16),
  1045. null,
  1046. ];
  1047. } else {
  1048. return false;
  1049. }
  1050.  
  1051. return ret;
  1052. }
  1053.  
  1054. if ((m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i))) {
  1055. // rgb(...) or rgba(...) notation
  1056.  
  1057. var par = m[1].split(",");
  1058. var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/;
  1059. var mR, mG, mB, mA;
  1060. if (
  1061. par.length >= 3 &&
  1062. (mR = par[0].match(re)) &&
  1063. (mG = par[1].match(re)) &&
  1064. (mB = par[2].match(re))
  1065. ) {
  1066. ret.format = "rgb";
  1067. ret.rgba = [
  1068. parseFloat(mR[1]) || 0,
  1069. parseFloat(mG[1]) || 0,
  1070. parseFloat(mB[1]) || 0,
  1071. null,
  1072. ];
  1073.  
  1074. if (par.length >= 4 && (mA = par[3].match(re))) {
  1075. ret.format = "rgba";
  1076. ret.rgba[3] = parseFloat(mA[1]) || 0;
  1077. }
  1078. return ret;
  1079. }
  1080. }
  1081.  
  1082. return false;
  1083. },
  1084.  
  1085. parsePaletteValue: function (mixed) {
  1086. var vals = [];
  1087.  
  1088. if (typeof mixed === "string") {
  1089. // input is a string of space separated color values
  1090. // rgb() and rgba() may contain spaces too, so let's find all color values by regex
  1091. mixed.replace(
  1092. /#[0-9A-F]{3}\b|#[0-9A-F]{6}([0-9A-F]{2})?\b|rgba?\(([^)]*)\)/gi,
  1093. function (val) {
  1094. vals.push(val);
  1095. }
  1096. );
  1097. } else if (Array.isArray(mixed)) {
  1098. // input is an array of color values
  1099. vals = mixed;
  1100. }
  1101.  
  1102. // convert all values into uniform color format
  1103.  
  1104. var colors = [];
  1105.  
  1106. for (var i = 0; i < vals.length; i++) {
  1107. var color = jsc.parseColorString(vals[i]);
  1108. if (color) {
  1109. colors.push(color);
  1110. }
  1111. }
  1112.  
  1113. return colors;
  1114. },
  1115.  
  1116. containsTranparentColor: function (colors) {
  1117. for (var i = 0; i < colors.length; i++) {
  1118. var a = colors[i].rgba[3];
  1119. if (a !== null && a < 1.0) {
  1120. return true;
  1121. }
  1122. }
  1123. return false;
  1124. },
  1125.  
  1126. isAlphaFormat: function (format) {
  1127. switch (format.toLowerCase()) {
  1128. case "hexa":
  1129. case "rgba":
  1130. return true;
  1131. }
  1132. return false;
  1133. },
  1134.  
  1135. // Canvas scaling for retina displays
  1136. //
  1137. // adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/
  1138. //
  1139. scaleCanvasForHighDPR: function (canvas) {
  1140. var dpr = window.devicePixelRatio || 1;
  1141. canvas.width *= dpr;
  1142. canvas.height *= dpr;
  1143. var ctx = canvas.getContext("2d");
  1144. ctx.scale(dpr, dpr);
  1145. },
  1146.  
  1147. genColorPreviewCanvas: function (
  1148. color,
  1149. separatorPos,
  1150. specWidth,
  1151. scaleForHighDPR
  1152. ) {
  1153. var sepW = Math.round(jsc.pub.previewSeparator.length);
  1154. var sqSize = jsc.pub.chessboardSize;
  1155. var sqColor1 = jsc.pub.chessboardColor1;
  1156. var sqColor2 = jsc.pub.chessboardColor2;
  1157.  
  1158. var cWidth = specWidth ? specWidth : sqSize * 2;
  1159. var cHeight = sqSize * 2;
  1160.  
  1161. var canvas = jsc.createEl("canvas");
  1162. var ctx = canvas.getContext("2d");
  1163.  
  1164. canvas.width = cWidth;
  1165. canvas.height = cHeight;
  1166. if (scaleForHighDPR) {
  1167. jsc.scaleCanvasForHighDPR(canvas);
  1168. }
  1169.  
  1170. // transparency chessboard - background
  1171. ctx.fillStyle = sqColor1;
  1172. ctx.fillRect(0, 0, cWidth, cHeight);
  1173.  
  1174. // transparency chessboard - squares
  1175. ctx.fillStyle = sqColor2;
  1176. for (var x = 0; x < cWidth; x += sqSize * 2) {
  1177. ctx.fillRect(x, 0, sqSize, sqSize);
  1178. ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize);
  1179. }
  1180.  
  1181. if (color) {
  1182. // actual color in foreground
  1183. ctx.fillStyle = color;
  1184. ctx.fillRect(0, 0, cWidth, cHeight);
  1185. }
  1186.  
  1187. var start = null;
  1188. switch (separatorPos) {
  1189. case "left":
  1190. start = 0;
  1191. ctx.clearRect(0, 0, sepW / 2, cHeight);
  1192. break;
  1193. case "right":
  1194. start = cWidth - sepW;
  1195. ctx.clearRect(cWidth - sepW / 2, 0, sepW / 2, cHeight);
  1196. break;
  1197. }
  1198. if (start !== null) {
  1199. ctx.lineWidth = 1;
  1200. for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) {
  1201. ctx.beginPath();
  1202. ctx.strokeStyle = jsc.pub.previewSeparator[i];
  1203. ctx.moveTo(0.5 + start + i, 0);
  1204. ctx.lineTo(0.5 + start + i, cHeight);
  1205. ctx.stroke();
  1206. }
  1207. }
  1208.  
  1209. return {
  1210. canvas: canvas,
  1211. width: cWidth,
  1212. height: cHeight,
  1213. };
  1214. },
  1215.  
  1216. // if position or width is not set => fill the entire element (0%-100%)
  1217. genColorPreviewGradient: function (color, position, width) {
  1218. var params = [];
  1219.  
  1220. if (position && width) {
  1221. params = [
  1222. "to " + { left: "right", right: "left" }[position],
  1223. color + " 0%",
  1224. color + " " + width + "px",
  1225. "rgba(0,0,0,0) " + (width + 1) + "px",
  1226. "rgba(0,0,0,0) 100%",
  1227. ];
  1228. } else {
  1229. params = ["to right", color + " 0%", color + " 100%"];
  1230. }
  1231.  
  1232. return jsc.linearGradient.apply(this, params);
  1233. },
  1234.  
  1235. redrawPosition: function () {
  1236. if (!jsc.picker || !jsc.picker.owner) {
  1237. return; // picker is not shown
  1238. }
  1239.  
  1240. var thisObj = jsc.picker.owner;
  1241.  
  1242. if (thisObj.container !== window.document.body) {
  1243. jsc._drawPosition(thisObj, 0, 0, "relative", false);
  1244. } else {
  1245. var tp, vp;
  1246.  
  1247. if (thisObj.fixed) {
  1248. // Fixed elements are positioned relative to viewport,
  1249. // therefore we can ignore the scroll offset
  1250. tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
  1251. vp = [0, 0]; // view pos
  1252. } else {
  1253. tp = jsc.getElementPos(thisObj.targetElement); // target pos
  1254. vp = jsc.getViewPos(); // view pos
  1255. }
  1256.  
  1257. var ts = jsc.getElementSize(thisObj.targetElement); // target size
  1258. var vs = jsc.getViewSize(); // view size
  1259. var pd = jsc.getPickerDims(thisObj);
  1260. var ps = [pd.borderW, pd.borderH]; // picker outer size
  1261. var a, b, c;
  1262. switch (thisObj.position.toLowerCase()) {
  1263. case "left":
  1264. a = 1;
  1265. b = 0;
  1266. c = -1;
  1267. break;
  1268. case "right":
  1269. a = 1;
  1270. b = 0;
  1271. c = 1;
  1272. break;
  1273. case "top":
  1274. a = 0;
  1275. b = 1;
  1276. c = -1;
  1277. break;
  1278. default:
  1279. a = 0;
  1280. b = 1;
  1281. c = 1;
  1282. break;
  1283. }
  1284. var l = (ts[b] + ps[b]) / 2;
  1285.  
  1286. // compute picker position
  1287. if (!thisObj.smartPosition) {
  1288. var pp = [tp[a], tp[b] + ts[b] - l + l * c];
  1289. } else {
  1290. var pp = [
  1291. -vp[a] + tp[a] + ps[a] > vs[a]
  1292. ? -vp[a] + tp[a] + ts[a] / 2 > vs[a] / 2 &&
  1293. tp[a] + ts[a] - ps[a] >= 0
  1294. ? tp[a] + ts[a] - ps[a]
  1295. : tp[a]
  1296. : tp[a],
  1297. -vp[b] + tp[b] + ts[b] + ps[b] - l + l * c > vs[b]
  1298. ? -vp[b] + tp[b] + ts[b] / 2 > vs[b] / 2 &&
  1299. tp[b] + ts[b] - l - l * c >= 0
  1300. ? tp[b] + ts[b] - l - l * c
  1301. : tp[b] + ts[b] - l + l * c
  1302. : tp[b] + ts[b] - l + l * c >= 0
  1303. ? tp[b] + ts[b] - l + l * c
  1304. : tp[b] + ts[b] - l - l * c,
  1305. ];
  1306. }
  1307.  
  1308. var x = pp[a];
  1309. var y = pp[b];
  1310. var positionValue = thisObj.fixed ? "fixed" : "absolute";
  1311. var contractShadow =
  1312. (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
  1313. pp[1] + ps[1] < tp[1] + ts[1];
  1314.  
  1315. jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
  1316. }
  1317. },
  1318.  
  1319. _drawPosition: function (thisObj, x, y, positionValue, contractShadow) {
  1320. var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px
  1321.  
  1322. jsc.picker.wrap.style.position = positionValue;
  1323.  
  1324. if (
  1325. // To avoid unnecessary repositioning during scroll
  1326. Math.round(parseFloat(jsc.picker.wrap.style.left)) !==
  1327. Math.round(x) ||
  1328. Math.round(parseFloat(jsc.picker.wrap.style.top)) !== Math.round(y)
  1329. ) {
  1330. jsc.picker.wrap.style.left = x + "px";
  1331. jsc.picker.wrap.style.top = y + "px";
  1332. }
  1333.  
  1334. jsc.setBoxShadow(
  1335. jsc.picker.boxS,
  1336. thisObj.shadow
  1337. ? new jsc.BoxShadow(
  1338. 0,
  1339. vShadow,
  1340. thisObj.shadowBlur,
  1341. 0,
  1342. thisObj.shadowColor
  1343. )
  1344. : null
  1345. );
  1346. },
  1347.  
  1348. getPickerDims: function (thisObj) {
  1349. var w = 2 * thisObj.controlBorderWidth + thisObj.width;
  1350. var h = 2 * thisObj.controlBorderWidth + thisObj.height;
  1351.  
  1352. var sliderSpace =
  1353. 2 * thisObj.controlBorderWidth +
  1354. 2 * jsc.getControlPadding(thisObj) +
  1355. thisObj.sliderSize;
  1356.  
  1357. if (jsc.getSliderChannel(thisObj)) {
  1358. w += sliderSpace;
  1359. }
  1360. if (thisObj.hasAlphaChannel()) {
  1361. w += sliderSpace;
  1362. }
  1363.  
  1364. var pal = jsc.getPaletteDims(thisObj, w);
  1365.  
  1366. if (pal.height) {
  1367. h += pal.height + thisObj.padding;
  1368. }
  1369. if (thisObj.closeButton) {
  1370. h +=
  1371. 2 * thisObj.controlBorderWidth +
  1372. thisObj.padding +
  1373. thisObj.buttonHeight;
  1374. }
  1375.  
  1376. var pW = w + 2 * thisObj.padding;
  1377. var pH = h + 2 * thisObj.padding;
  1378.  
  1379. return {
  1380. contentW: w,
  1381. contentH: h,
  1382. paddedW: pW,
  1383. paddedH: pH,
  1384. borderW: pW + 2 * thisObj.borderWidth,
  1385. borderH: pH + 2 * thisObj.borderWidth,
  1386. palette: pal,
  1387. };
  1388. },
  1389.  
  1390. getPaletteDims: function (thisObj, width) {
  1391. var cols = 0,
  1392. rows = 0,
  1393. cellW = 0,
  1394. cellH = 0,
  1395. height = 0;
  1396. var sampleCount = thisObj._palette ? thisObj._palette.length : 0;
  1397.  
  1398. if (sampleCount) {
  1399. cols = thisObj.paletteCols;
  1400. rows = cols > 0 ? Math.ceil(sampleCount / cols) : 0;
  1401.  
  1402. // color sample's dimensions (includes border)
  1403. cellW = Math.max(
  1404. 1,
  1405. Math.floor((width - (cols - 1) * thisObj.paletteSpacing) / cols)
  1406. );
  1407. cellH = thisObj.paletteHeight
  1408. ? Math.min(thisObj.paletteHeight, cellW)
  1409. : cellW;
  1410. }
  1411.  
  1412. if (rows) {
  1413. height = rows * cellH + (rows - 1) * thisObj.paletteSpacing;
  1414. }
  1415.  
  1416. return {
  1417. cols: cols,
  1418. rows: rows,
  1419. cellW: cellW,
  1420. cellH: cellH,
  1421. width: width,
  1422. height: height,
  1423. };
  1424. },
  1425.  
  1426. getControlPadding: function (thisObj) {
  1427. return Math.max(
  1428. thisObj.padding / 2,
  1429. 2 * thisObj.pointerBorderWidth +
  1430. thisObj.pointerThickness -
  1431. thisObj.controlBorderWidth
  1432. );
  1433. },
  1434.  
  1435. getPadYChannel: function (thisObj) {
  1436. switch (thisObj.mode.charAt(1).toLowerCase()) {
  1437. case "v":
  1438. return "v";
  1439. break;
  1440. }
  1441. return "s";
  1442. },
  1443.  
  1444. getSliderChannel: function (thisObj) {
  1445. if (thisObj.mode.length > 2) {
  1446. switch (thisObj.mode.charAt(2).toLowerCase()) {
  1447. case "s":
  1448. return "s";
  1449. break;
  1450. case "v":
  1451. return "v";
  1452. break;
  1453. }
  1454. }
  1455. return null;
  1456. },
  1457.  
  1458. // calls function specified in picker's property
  1459. triggerCallback: function (thisObj, prop) {
  1460. if (!thisObj[prop]) {
  1461. return; // callback func not specified
  1462. }
  1463. var callback = null;
  1464.  
  1465. if (typeof thisObj[prop] === "string") {
  1466. // string with code
  1467. try {
  1468. callback = new Function(thisObj[prop]);
  1469. } catch (e) {
  1470. console.error(e);
  1471. }
  1472. } else {
  1473. // function
  1474. callback = thisObj[prop];
  1475. }
  1476.  
  1477. if (callback) {
  1478. callback.call(thisObj);
  1479. }
  1480. },
  1481.  
  1482. // Triggers a color change related event(s) on all picker instances.
  1483. // It is possible to specify multiple events separated with a space.
  1484. triggerGlobal: function (eventNames) {
  1485. var inst = jsc.getInstances();
  1486. for (var i = 0; i < inst.length; i += 1) {
  1487. inst[i].trigger(eventNames);
  1488. }
  1489. },
  1490.  
  1491. _pointerMoveEvent: {
  1492. mouse: "mousemove",
  1493. touch: "touchmove",
  1494. },
  1495. _pointerEndEvent: {
  1496. mouse: "mouseup",
  1497. touch: "touchend",
  1498. },
  1499.  
  1500. _pointerOrigin: null,
  1501.  
  1502. onDocumentKeyUp: function (e) {
  1503. if (["Tab", "Escape"].indexOf(jsc.eventKey(e)) !== -1) {
  1504. if (jsc.picker && jsc.picker.owner) {
  1505. jsc.picker.owner.tryHide();
  1506. }
  1507. }
  1508. },
  1509.  
  1510. onWindowResize: function (e) {
  1511. jsc.redrawPosition();
  1512. },
  1513.  
  1514. onWindowScroll: function (e) {
  1515. jsc.redrawPosition();
  1516. },
  1517.  
  1518. onParentScroll: function (e) {
  1519. // hide the picker when one of the parent elements is scrolled
  1520. if (jsc.picker && jsc.picker.owner) {
  1521. jsc.picker.owner.tryHide();
  1522. }
  1523. },
  1524.  
  1525. onDocumentMouseDown: function (e) {
  1526. var target = e.target || e.srcElement;
  1527.  
  1528. if (target.jscolor && target.jscolor instanceof jsc.pub) {
  1529. // clicked targetElement -> show picker
  1530. if (target.jscolor.showOnClick && !target.disabled) {
  1531. target.jscolor.show();
  1532. }
  1533. } else if (jsc.getData(target, "gui")) {
  1534. // clicked jscolor's GUI element
  1535. var control = jsc.getData(target, "control");
  1536. if (control) {
  1537. // jscolor's control
  1538. jsc.onControlPointerStart(
  1539. e,
  1540. target,
  1541. jsc.getData(target, "control"),
  1542. "mouse"
  1543. );
  1544. }
  1545. } else {
  1546. // mouse is outside the picker's controls -> hide the color picker!
  1547. if (jsc.picker && jsc.picker.owner) {
  1548. jsc.picker.owner.tryHide();
  1549. }
  1550. }
  1551. },
  1552.  
  1553. onPickerTouchStart: function (e) {
  1554. var target = e.target || e.srcElement;
  1555.  
  1556. if (jsc.getData(target, "control")) {
  1557. jsc.onControlPointerStart(
  1558. e,
  1559. target,
  1560. jsc.getData(target, "control"),
  1561. "touch"
  1562. );
  1563. }
  1564. },
  1565.  
  1566. onControlPointerStart: function (e, target, controlName, pointerType) {
  1567. var thisObj = jsc.getData(target, "instance");
  1568.  
  1569. jsc.preventDefault(e);
  1570.  
  1571. var registerDragEvents = function (doc, offset) {
  1572. jsc.attachGroupEvent(
  1573. "drag",
  1574. doc,
  1575. jsc._pointerMoveEvent[pointerType],
  1576. jsc.onDocumentPointerMove(
  1577. e,
  1578. target,
  1579. controlName,
  1580. pointerType,
  1581. offset
  1582. )
  1583. );
  1584. jsc.attachGroupEvent(
  1585. "drag",
  1586. doc,
  1587. jsc._pointerEndEvent[pointerType],
  1588. jsc.onDocumentPointerEnd(e, target, controlName, pointerType)
  1589. );
  1590. };
  1591.  
  1592. registerDragEvents(window.document, [0, 0]);
  1593.  
  1594. if (window.parent && window.frameElement) {
  1595. var rect = window.frameElement.getBoundingClientRect();
  1596. var ofs = [-rect.left, -rect.top];
  1597. registerDragEvents(window.parent.window.document, ofs);
  1598. }
  1599.  
  1600. var abs = jsc.getAbsPointerPos(e);
  1601. var rel = jsc.getRelPointerPos(e);
  1602. jsc._pointerOrigin = {
  1603. x: abs.x - rel.x,
  1604. y: abs.y - rel.y,
  1605. };
  1606.  
  1607. switch (controlName) {
  1608. case "pad":
  1609. // if the value slider is at the bottom, move it up
  1610. if (
  1611. jsc.getSliderChannel(thisObj) === "v" &&
  1612. thisObj.channels.v === 0
  1613. ) {
  1614. thisObj.fromHSVA(null, null, 100, null);
  1615. }
  1616. jsc.setPad(thisObj, e, 0, 0);
  1617. break;
  1618.  
  1619. case "sld":
  1620. jsc.setSld(thisObj, e, 0);
  1621. break;
  1622.  
  1623. case "asld":
  1624. jsc.setASld(thisObj, e, 0);
  1625. break;
  1626. }
  1627. thisObj.trigger("input");
  1628. },
  1629.  
  1630. onDocumentPointerMove: function (
  1631. e,
  1632. target,
  1633. controlName,
  1634. pointerType,
  1635. offset
  1636. ) {
  1637. return function (e) {
  1638. var thisObj = jsc.getData(target, "instance");
  1639. switch (controlName) {
  1640. case "pad":
  1641. jsc.setPad(thisObj, e, offset[0], offset[1]);
  1642. break;
  1643.  
  1644. case "sld":
  1645. jsc.setSld(thisObj, e, offset[1]);
  1646. break;
  1647.  
  1648. case "asld":
  1649. jsc.setASld(thisObj, e, offset[1]);
  1650. break;
  1651. }
  1652. thisObj.trigger("input");
  1653. };
  1654. },
  1655.  
  1656. onDocumentPointerEnd: function (e, target, controlName, pointerType) {
  1657. return function (e) {
  1658. var thisObj = jsc.getData(target, "instance");
  1659. jsc.detachGroupEvents("drag");
  1660.  
  1661. // Always trigger changes AFTER detaching outstanding mouse handlers,
  1662. // in case some color change that occured in user-defined onChange/onInput handler
  1663. // intruded into current mouse events
  1664. thisObj.trigger("input");
  1665. thisObj.trigger("change");
  1666. };
  1667. },
  1668.  
  1669. onPaletteSampleClick: function (e) {
  1670. var target = e.currentTarget;
  1671. var thisObj = jsc.getData(target, "instance");
  1672. var color = jsc.getData(target, "color");
  1673.  
  1674. // when format is flexible, use the original format of this color sample
  1675. if (thisObj.format.toLowerCase() === "any") {
  1676. thisObj._setFormat(color.format); // adapt format
  1677. if (!jsc.isAlphaFormat(thisObj.getFormat())) {
  1678. color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
  1679. }
  1680. }
  1681.  
  1682. // if this color doesn't specify alpha, use alpha of 1.0 (if applicable)
  1683. if (color.rgba[3] === null) {
  1684. if (
  1685. thisObj.paletteSetsAlpha === true ||
  1686. (thisObj.paletteSetsAlpha === "auto" &&
  1687. thisObj._paletteHasTransparency)
  1688. ) {
  1689. color.rgba[3] = 1.0;
  1690. }
  1691. }
  1692.  
  1693. thisObj.fromRGBA.apply(thisObj, color.rgba);
  1694.  
  1695. thisObj.trigger("input");
  1696. thisObj.trigger("change");
  1697.  
  1698. if (thisObj.hideOnPaletteClick) {
  1699. thisObj.hide();
  1700. }
  1701. },
  1702.  
  1703. setPad: function (thisObj, e, ofsX, ofsY) {
  1704. var pointerAbs = jsc.getAbsPointerPos(e);
  1705. var x =
  1706. ofsX +
  1707. pointerAbs.x -
  1708. jsc._pointerOrigin.x -
  1709. thisObj.padding -
  1710. thisObj.controlBorderWidth;
  1711. var y =
  1712. ofsY +
  1713. pointerAbs.y -
  1714. jsc._pointerOrigin.y -
  1715. thisObj.padding -
  1716. thisObj.controlBorderWidth;
  1717.  
  1718. var xVal = x * (360 / (thisObj.width - 1));
  1719. var yVal = 100 - y * (100 / (thisObj.height - 1));
  1720.  
  1721. switch (jsc.getPadYChannel(thisObj)) {
  1722. case "s":
  1723. thisObj.fromHSVA(xVal, yVal, null, null);
  1724. break;
  1725. case "v":
  1726. thisObj.fromHSVA(xVal, null, yVal, null);
  1727. break;
  1728. }
  1729. },
  1730.  
  1731. setSld: function (thisObj, e, ofsY) {
  1732. var pointerAbs = jsc.getAbsPointerPos(e);
  1733. var y =
  1734. ofsY +
  1735. pointerAbs.y -
  1736. jsc._pointerOrigin.y -
  1737. thisObj.padding -
  1738. thisObj.controlBorderWidth;
  1739. var yVal = 100 - y * (100 / (thisObj.height - 1));
  1740.  
  1741. switch (jsc.getSliderChannel(thisObj)) {
  1742. case "s":
  1743. thisObj.fromHSVA(null, yVal, null, null);
  1744. break;
  1745. case "v":
  1746. thisObj.fromHSVA(null, null, yVal, null);
  1747. break;
  1748. }
  1749. },
  1750.  
  1751. setASld: function (thisObj, e, ofsY) {
  1752. var pointerAbs = jsc.getAbsPointerPos(e);
  1753. var y =
  1754. ofsY +
  1755. pointerAbs.y -
  1756. jsc._pointerOrigin.y -
  1757. thisObj.padding -
  1758. thisObj.controlBorderWidth;
  1759. var yVal = 1.0 - y * (1.0 / (thisObj.height - 1));
  1760.  
  1761. if (yVal < 1.0) {
  1762. // if format is flexible and the current format doesn't support alpha, switch to a suitable one
  1763. var fmt = thisObj.getFormat();
  1764. if (
  1765. thisObj.format.toLowerCase() === "any" &&
  1766. !jsc.isAlphaFormat(fmt)
  1767. ) {
  1768. thisObj._setFormat(fmt === "hex" ? "hexa" : "rgba");
  1769. }
  1770. }
  1771.  
  1772. thisObj.fromHSVA(null, null, null, yVal);
  1773. },
  1774.  
  1775. createPadCanvas: function () {
  1776. var ret = {
  1777. elm: null,
  1778. draw: null,
  1779. };
  1780.  
  1781. var canvas = jsc.createEl("canvas");
  1782. var ctx = canvas.getContext("2d");
  1783.  
  1784. var drawFunc = function (width, height, type) {
  1785. canvas.width = width;
  1786. canvas.height = height;
  1787.  
  1788. ctx.clearRect(0, 0, canvas.width, canvas.height);
  1789.  
  1790. var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
  1791. hGrad.addColorStop(0 / 6, "#F00");
  1792. hGrad.addColorStop(1 / 6, "#FF0");
  1793. hGrad.addColorStop(2 / 6, "#0F0");
  1794. hGrad.addColorStop(3 / 6, "#0FF");
  1795. hGrad.addColorStop(4 / 6, "#00F");
  1796. hGrad.addColorStop(5 / 6, "#F0F");
  1797. hGrad.addColorStop(6 / 6, "#F00");
  1798.  
  1799. ctx.fillStyle = hGrad;
  1800. ctx.fillRect(0, 0, canvas.width, canvas.height);
  1801.  
  1802. var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  1803. switch (type.toLowerCase()) {
  1804. case "s":
  1805. vGrad.addColorStop(0, "rgba(255,255,255,0)");
  1806. vGrad.addColorStop(1, "rgba(255,255,255,1)");
  1807. break;
  1808. case "v":
  1809. vGrad.addColorStop(0, "rgba(0,0,0,0)");
  1810. vGrad.addColorStop(1, "rgba(0,0,0,1)");
  1811. break;
  1812. }
  1813. ctx.fillStyle = vGrad;
  1814. ctx.fillRect(0, 0, canvas.width, canvas.height);
  1815. };
  1816.  
  1817. ret.elm = canvas;
  1818. ret.draw = drawFunc;
  1819.  
  1820. return ret;
  1821. },
  1822.  
  1823. createSliderGradient: function () {
  1824. var ret = {
  1825. elm: null,
  1826. draw: null,
  1827. };
  1828.  
  1829. var canvas = jsc.createEl("canvas");
  1830. var ctx = canvas.getContext("2d");
  1831.  
  1832. var drawFunc = function (width, height, color1, color2) {
  1833. canvas.width = width;
  1834. canvas.height = height;
  1835.  
  1836. ctx.clearRect(0, 0, canvas.width, canvas.height);
  1837.  
  1838. var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  1839. grad.addColorStop(0, color1);
  1840. grad.addColorStop(1, color2);
  1841.  
  1842. ctx.fillStyle = grad;
  1843. ctx.fillRect(0, 0, canvas.width, canvas.height);
  1844. };
  1845.  
  1846. ret.elm = canvas;
  1847. ret.draw = drawFunc;
  1848.  
  1849. return ret;
  1850. },
  1851.  
  1852. createASliderGradient: function () {
  1853. var ret = {
  1854. elm: null,
  1855. draw: null,
  1856. };
  1857.  
  1858. var canvas = jsc.createEl("canvas");
  1859. var ctx = canvas.getContext("2d");
  1860.  
  1861. var drawFunc = function (width, height, color) {
  1862. canvas.width = width;
  1863. canvas.height = height;
  1864.  
  1865. ctx.clearRect(0, 0, canvas.width, canvas.height);
  1866.  
  1867. var sqSize = canvas.width / 2;
  1868. var sqColor1 = jsc.pub.chessboardColor1;
  1869. var sqColor2 = jsc.pub.chessboardColor2;
  1870.  
  1871. // dark gray background
  1872. ctx.fillStyle = sqColor1;
  1873. ctx.fillRect(0, 0, canvas.width, canvas.height);
  1874.  
  1875. if (sqSize > 0) {
  1876. // to avoid infinite loop
  1877. for (var y = 0; y < canvas.height; y += sqSize * 2) {
  1878. // light gray squares
  1879. ctx.fillStyle = sqColor2;
  1880. ctx.fillRect(0, y, sqSize, sqSize);
  1881. ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize);
  1882. }
  1883. }
  1884.  
  1885. var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  1886. grad.addColorStop(0, color);
  1887. grad.addColorStop(1, "rgba(0,0,0,0)");
  1888.  
  1889. ctx.fillStyle = grad;
  1890. ctx.fillRect(0, 0, canvas.width, canvas.height);
  1891. };
  1892.  
  1893. ret.elm = canvas;
  1894. ret.draw = drawFunc;
  1895.  
  1896. return ret;
  1897. },
  1898.  
  1899. BoxShadow: (function () {
  1900. var BoxShadow = function (
  1901. hShadow,
  1902. vShadow,
  1903. blur,
  1904. spread,
  1905. color,
  1906. inset
  1907. ) {
  1908. this.hShadow = hShadow;
  1909. this.vShadow = vShadow;
  1910. this.blur = blur;
  1911. this.spread = spread;
  1912. this.color = color;
  1913. this.inset = !!inset;
  1914. };
  1915.  
  1916. BoxShadow.prototype.toString = function () {
  1917. var vals = [
  1918. Math.round(this.hShadow) + "px",
  1919. Math.round(this.vShadow) + "px",
  1920. Math.round(this.blur) + "px",
  1921. Math.round(this.spread) + "px",
  1922. this.color,
  1923. ];
  1924. if (this.inset) {
  1925. vals.push("inset");
  1926. }
  1927. return vals.join(" ");
  1928. };
  1929.  
  1930. return BoxShadow;
  1931. })(),
  1932.  
  1933. flags: {
  1934. leaveValue: 1 << 0,
  1935. leaveAlpha: 1 << 1,
  1936. leavePreview: 1 << 2,
  1937. },
  1938.  
  1939. enumOpts: {
  1940. format: ["auto", "any", "hex", "hexa", "rgb", "rgba"],
  1941. previewPosition: ["left", "right"],
  1942. mode: ["hsv", "hvs", "hs", "hv"],
  1943. position: ["left", "right", "top", "bottom"],
  1944. alphaChannel: ["auto", true, false],
  1945. paletteSetsAlpha: ["auto", true, false],
  1946. },
  1947.  
  1948. deprecatedOpts: {
  1949. // <old_option>: <new_option> (<new_option> can be null)
  1950. styleElement: "previewElement",
  1951. onFineChange: "onInput",
  1952. overwriteImportant: "forceStyle",
  1953. closable: "closeButton",
  1954. insetWidth: "controlBorderWidth",
  1955. insetColor: "controlBorderColor",
  1956. refine: null,
  1957. },
  1958.  
  1959. docsRef: " " + "See https://jscolor.com/docs/",
  1960.  
  1961. //
  1962. // Usage:
  1963. // var myPicker = new JSColor(<targetElement> [, <options>])
  1964. //
  1965. // (constructor is accessible via both 'jscolor' and 'JSColor' name)
  1966. //
  1967.  
  1968. pub: function (targetElement, opts) {
  1969. var THIS = this;
  1970.  
  1971. if (!opts) {
  1972. opts = {};
  1973. }
  1974.  
  1975. this.channels = {
  1976. r: 255, // red [0-255]
  1977. g: 255, // green [0-255]
  1978. b: 255, // blue [0-255]
  1979. h: 0, // hue [0-360]
  1980. s: 0, // saturation [0-100]
  1981. v: 100, // value (brightness) [0-100]
  1982. a: 1.0, // alpha (opacity) [0.0 - 1.0]
  1983. };
  1984.  
  1985. // General options
  1986. //
  1987. this.format = "auto"; // 'auto' | 'any' | 'hex' | 'hexa' | 'rgb' | 'rgba' - Format of the input/output value
  1988. this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel()
  1989. this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', <value>)
  1990. this.random = false; // whether to randomize the initial color. Either true | false, or an array of ranges: [minV, maxV, minS, maxS, minH, maxH, minA, maxA]
  1991. this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code.
  1992. this.onInput = undefined; // called repeatedly as the color is being changed, e.g. while dragging a slider. Value can be either a function or a string with JS code.
  1993. this.valueElement = undefined; // element that will be used to display and input the color value
  1994. this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value
  1995. this.previewElement = undefined; // element that will preview the picked color using CSS background
  1996. this.previewPosition = "left"; // 'left' | 'right' - position of the color preview in previewElement
  1997. this.previewSize = 32; // (px) width of the color preview displayed in previewElement
  1998. this.previewPadding = 8; // (px) space between color preview and content of the previewElement
  1999. this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty.
  2000. this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format)
  2001. this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format)
  2002. this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag
  2003.  
  2004. // Color Picker options
  2005. //
  2006. this.width = 181; // width of the color spectrum (in px)
  2007. this.height = 101; // height of the color spectrum (in px)
  2008. this.mode = "HSV"; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls
  2009. this.alphaChannel = "auto"; // 'auto' | true | false - if alpha channel is enabled, the alpha slider will be visible. If 'auto', it will be determined according to color format
  2010. this.position = "bottom"; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element
  2011. this.smartPosition = true; // automatically change picker position when there is not enough space for it
  2012. this.showOnClick = true; // whether to show the picker when user clicks its target element
  2013. this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document)
  2014. this.palette = []; // colors to be displayed in the palette, specified as an array or a string of space separated color values (in any supported format)
  2015. this.paletteCols = 10; // number of columns in the palette
  2016. this.paletteSetsAlpha = "auto"; // 'auto' | true | false - if true, palette colors that don't specify alpha will set alpha to 1.0
  2017. this.paletteHeight = 16; // maximum height (px) of a row in the palette
  2018. this.paletteSpacing = 4; // distance (px) between color samples in the palette
  2019. this.hideOnPaletteClick = false; // when set to true, clicking the palette will also hide the color picker
  2020. this.sliderSize = 16; // px
  2021. this.crossSize = 8; // px
  2022. this.closeButton = false; // whether to display the Close button
  2023. this.closeText = "Close";
  2024. this.buttonColor = "rgba(0,0,0,1)"; // CSS color
  2025. this.buttonHeight = 18; // px
  2026. this.padding = 12; // px
  2027. this.backgroundColor = "rgba(255,255,255,1)"; // CSS color
  2028. this.borderWidth = 1; // px
  2029. this.borderColor = "rgba(187,187,187,1)"; // CSS color
  2030. this.borderRadius = 8; // px
  2031. this.controlBorderWidth = 1; // px
  2032. this.controlBorderColor = "rgba(187,187,187,1)"; // CSS color
  2033. this.shadow = true; // whether to display a shadow
  2034. this.shadowBlur = 15; // px
  2035. this.shadowColor = "rgba(0,0,0,0.2)"; // CSS color
  2036. this.pointerColor = "rgba(76,76,76,1)"; // CSS color
  2037. this.pointerBorderWidth = 1; // px
  2038. this.pointerBorderColor = "rgba(255,255,255,1)"; // CSS color
  2039. this.pointerThickness = 2; // px
  2040. this.zIndex = 5000;
  2041. this.container = undefined; // where to append the color picker (BODY element by default)
  2042.  
  2043. // Experimental
  2044. //
  2045. this.minS = 0; // min allowed saturation (0 - 100)
  2046. this.maxS = 100; // max allowed saturation (0 - 100)
  2047. this.minV = 0; // min allowed value (brightness) (0 - 100)
  2048. this.maxV = 100; // max allowed value (brightness) (0 - 100)
  2049. this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0)
  2050. this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0)
  2051.  
  2052. // Getter: option(name)
  2053. // Setter: option(name, value)
  2054. // option({name:value, ...})
  2055. //
  2056. this.option = function () {
  2057. if (!arguments.length) {
  2058. throw new Error("No option specified");
  2059. }
  2060.  
  2061. if (arguments.length === 1 && typeof arguments[0] === "string") {
  2062. // getting a single option
  2063. try {
  2064. return getOption(arguments[0]);
  2065. } catch (e) {
  2066. console.warn(e);
  2067. }
  2068. return false;
  2069. } else if (
  2070. arguments.length >= 2 &&
  2071. typeof arguments[0] === "string"
  2072. ) {
  2073. // setting a single option
  2074. try {
  2075. if (!setOption(arguments[0], arguments[1])) {
  2076. return false;
  2077. }
  2078. } catch (e) {
  2079. console.warn(e);
  2080. return false;
  2081. }
  2082. this.redraw(); // immediately redraws the picker, if it's displayed
  2083. this.exposeColor(); // in case some preview-related or format-related option was changed
  2084. return true;
  2085. } else if (
  2086. arguments.length === 1 &&
  2087. typeof arguments[0] === "object"
  2088. ) {
  2089. // setting multiple options
  2090. var opts = arguments[0];
  2091. var success = true;
  2092. for (var opt in opts) {
  2093. if (opts.hasOwnProperty(opt)) {
  2094. try {
  2095. if (!setOption(opt, opts[opt])) {
  2096. success = false;
  2097. }
  2098. } catch (e) {
  2099. console.warn(e);
  2100. success = false;
  2101. }
  2102. }
  2103. }
  2104. this.redraw(); // immediately redraws the picker, if it's displayed
  2105. this.exposeColor(); // in case some preview-related or format-related option was changed
  2106. return success;
  2107. }
  2108.  
  2109. throw new Error("Invalid arguments");
  2110. };
  2111.  
  2112. // Getter: channel(name)
  2113. // Setter: channel(name, value)
  2114. //
  2115. this.channel = function (name, value) {
  2116. if (typeof name !== "string") {
  2117. throw new Error("Invalid value for channel name: " + name);
  2118. }
  2119.  
  2120. if (value === undefined) {
  2121. // getting channel value
  2122. if (!this.channels.hasOwnProperty(name.toLowerCase())) {
  2123. console.warn("Getting unknown channel: " + name);
  2124. return false;
  2125. }
  2126. return this.channels[name.toLowerCase()];
  2127. } else {
  2128. // setting channel value
  2129. var res = false;
  2130. switch (name.toLowerCase()) {
  2131. case "r":
  2132. res = this.fromRGBA(value, null, null, null);
  2133. break;
  2134. case "g":
  2135. res = this.fromRGBA(null, value, null, null);
  2136. break;
  2137. case "b":
  2138. res = this.fromRGBA(null, null, value, null);
  2139. break;
  2140. case "h":
  2141. res = this.fromHSVA(value, null, null, null);
  2142. break;
  2143. case "s":
  2144. res = this.fromHSVA(null, value, null, null);
  2145. break;
  2146. case "v":
  2147. res = this.fromHSVA(null, null, value, null);
  2148. break;
  2149. case "a":
  2150. res = this.fromHSVA(null, null, null, value);
  2151. break;
  2152. default:
  2153. console.warn("Setting unknown channel: " + name);
  2154. return false;
  2155. }
  2156. if (res) {
  2157. this.redraw(); // immediately redraws the picker, if it's displayed
  2158. return true;
  2159. }
  2160. }
  2161.  
  2162. return false;
  2163. };
  2164.  
  2165. // Triggers given input event(s) by:
  2166. // - executing on<Event> callback specified as picker's option
  2167. // - triggering standard DOM event listeners attached to the value element
  2168. //
  2169. // It is possible to specify multiple events separated with a space.
  2170. //
  2171. this.trigger = function (eventNames) {
  2172. var evs = jsc.strList(eventNames);
  2173. for (var i = 0; i < evs.length; i += 1) {
  2174. var ev = evs[i].toLowerCase();
  2175.  
  2176. // trigger a callback
  2177. var callbackProp = null;
  2178. switch (ev) {
  2179. case "input":
  2180. callbackProp = "onInput";
  2181. break;
  2182. case "change":
  2183. callbackProp = "onChange";
  2184. break;
  2185. }
  2186. if (callbackProp) {
  2187. jsc.triggerCallback(this, callbackProp);
  2188. }
  2189.  
  2190. // trigger standard DOM event listeners on the value element
  2191. jsc.triggerInputEvent(this.valueElement, ev, true, true);
  2192. }
  2193. };
  2194.  
  2195. // h: 0-360
  2196. // s: 0-100
  2197. // v: 0-100
  2198. // a: 0.0-1.0
  2199. //
  2200. this.fromHSVA = function (h, s, v, a, flags) {
  2201. // null = don't change
  2202. if (h === undefined) {
  2203. h = null;
  2204. }
  2205. if (s === undefined) {
  2206. s = null;
  2207. }
  2208. if (v === undefined) {
  2209. v = null;
  2210. }
  2211. if (a === undefined) {
  2212. a = null;
  2213. }
  2214.  
  2215. if (h !== null) {
  2216. if (isNaN(h)) {
  2217. return false;
  2218. }
  2219. this.channels.h = Math.max(0, Math.min(360, h));
  2220. }
  2221. if (s !== null) {
  2222. if (isNaN(s)) {
  2223. return false;
  2224. }
  2225. this.channels.s = Math.max(
  2226. 0,
  2227. Math.min(100, this.maxS, s),
  2228. this.minS
  2229. );
  2230. }
  2231. if (v !== null) {
  2232. if (isNaN(v)) {
  2233. return false;
  2234. }
  2235. this.channels.v = Math.max(
  2236. 0,
  2237. Math.min(100, this.maxV, v),
  2238. this.minV
  2239. );
  2240. }
  2241. if (a !== null) {
  2242. if (isNaN(a)) {
  2243. return false;
  2244. }
  2245. this.channels.a = this.hasAlphaChannel()
  2246. ? Math.max(0, Math.min(1, this.maxA, a), this.minA)
  2247. : 1.0; // if alpha channel is disabled, the color should stay 100% opaque
  2248. }
  2249.  
  2250. var rgb = jsc.HSV_RGB(
  2251. this.channels.h,
  2252. this.channels.s,
  2253. this.channels.v
  2254. );
  2255. this.channels.r = rgb[0];
  2256. this.channels.g = rgb[1];
  2257. this.channels.b = rgb[2];
  2258.  
  2259. this.exposeColor(flags);
  2260. return true;
  2261. };
  2262.  
  2263. // r: 0-255
  2264. // g: 0-255
  2265. // b: 0-255
  2266. // a: 0.0-1.0
  2267. //
  2268. this.fromRGBA = function (r, g, b, a, flags) {
  2269. // null = don't change
  2270. if (r === undefined) {
  2271. r = null;
  2272. }
  2273. if (g === undefined) {
  2274. g = null;
  2275. }
  2276. if (b === undefined) {
  2277. b = null;
  2278. }
  2279. if (a === undefined) {
  2280. a = null;
  2281. }
  2282.  
  2283. if (r !== null) {
  2284. if (isNaN(r)) {
  2285. return false;
  2286. }
  2287. r = Math.max(0, Math.min(255, r));
  2288. }
  2289. if (g !== null) {
  2290. if (isNaN(g)) {
  2291. return false;
  2292. }
  2293. g = Math.max(0, Math.min(255, g));
  2294. }
  2295. if (b !== null) {
  2296. if (isNaN(b)) {
  2297. return false;
  2298. }
  2299. b = Math.max(0, Math.min(255, b));
  2300. }
  2301. if (a !== null) {
  2302. if (isNaN(a)) {
  2303. return false;
  2304. }
  2305. this.channels.a = this.hasAlphaChannel()
  2306. ? Math.max(0, Math.min(1, this.maxA, a), this.minA)
  2307. : 1.0; // if alpha channel is disabled, the color should stay 100% opaque
  2308. }
  2309.  
  2310. var hsv = jsc.RGB_HSV(
  2311. r === null ? this.channels.r : r,
  2312. g === null ? this.channels.g : g,
  2313. b === null ? this.channels.b : b
  2314. );
  2315. if (hsv[0] !== null) {
  2316. this.channels.h = Math.max(0, Math.min(360, hsv[0]));
  2317. }
  2318. if (hsv[2] !== 0) {
  2319. // fully black color stays black through entire saturation range, so let's not change saturation
  2320. this.channels.s = Math.max(
  2321. 0,
  2322. this.minS,
  2323. Math.min(100, this.maxS, hsv[1])
  2324. );
  2325. }
  2326. this.channels.v = Math.max(
  2327. 0,
  2328. this.minV,
  2329. Math.min(100, this.maxV, hsv[2])
  2330. );
  2331.  
  2332. // update RGB according to final HSV, as some values might be trimmed
  2333. var rgb = jsc.HSV_RGB(
  2334. this.channels.h,
  2335. this.channels.s,
  2336. this.channels.v
  2337. );
  2338. this.channels.r = rgb[0];
  2339. this.channels.g = rgb[1];
  2340. this.channels.b = rgb[2];
  2341.  
  2342. this.exposeColor(flags);
  2343. return true;
  2344. };
  2345.  
  2346. // DEPRECATED. Use .fromHSVA() instead
  2347. //
  2348. this.fromHSV = function (h, s, v, flags) {
  2349. console.warn(
  2350. "fromHSV() method is DEPRECATED. Using fromHSVA() instead." +
  2351. jsc.docsRef
  2352. );
  2353. return this.fromHSVA(h, s, v, null, flags);
  2354. };
  2355.  
  2356. // DEPRECATED. Use .fromRGBA() instead
  2357. //
  2358. this.fromRGB = function (r, g, b, flags) {
  2359. console.warn(
  2360. "fromRGB() method is DEPRECATED. Using fromRGBA() instead." +
  2361. jsc.docsRef
  2362. );
  2363. return this.fromRGBA(r, g, b, null, flags);
  2364. };
  2365.  
  2366. this.fromString = function (str, flags) {
  2367. if (!this.required && str.trim() === "") {
  2368. // setting empty string to an optional color input
  2369. this.setPreviewElementBg(null);
  2370. this.setValueElementValue("");
  2371. return true;
  2372. }
  2373.  
  2374. var color = jsc.parseColorString(str);
  2375. if (!color) {
  2376. return false; // could not parse
  2377. }
  2378. if (this.format.toLowerCase() === "any") {
  2379. this._setFormat(color.format); // adapt format
  2380. if (!jsc.isAlphaFormat(this.getFormat())) {
  2381. color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
  2382. }
  2383. }
  2384. this.fromRGBA(
  2385. color.rgba[0],
  2386. color.rgba[1],
  2387. color.rgba[2],
  2388. color.rgba[3],
  2389. flags
  2390. );
  2391. return true;
  2392. };
  2393.  
  2394. this.randomize = function (
  2395. minV,
  2396. maxV,
  2397. minS,
  2398. maxS,
  2399. minH,
  2400. maxH,
  2401. minA,
  2402. maxA
  2403. ) {
  2404. if (minV === undefined) {
  2405. minV = 0;
  2406. }
  2407. if (maxV === undefined) {
  2408. maxV = 100;
  2409. }
  2410. if (minS === undefined) {
  2411. minS = 0;
  2412. }
  2413. if (maxS === undefined) {
  2414. maxS = 100;
  2415. }
  2416. if (minH === undefined) {
  2417. minH = 0;
  2418. }
  2419. if (maxH === undefined) {
  2420. maxH = 359;
  2421. }
  2422. if (minA === undefined) {
  2423. minA = 1;
  2424. }
  2425. if (maxA === undefined) {
  2426. maxA = 1;
  2427. }
  2428.  
  2429. this.fromHSVA(
  2430. minH + Math.floor(Math.random() * (maxH - minH + 1)),
  2431. minS + Math.floor(Math.random() * (maxS - minS + 1)),
  2432. minV + Math.floor(Math.random() * (maxV - minV + 1)),
  2433. (100 * minA +
  2434. Math.floor(Math.random() * (100 * (maxA - minA) + 1))) /
  2435. 100
  2436. );
  2437. };
  2438.  
  2439. this.toString = function (format) {
  2440. if (format === undefined) {
  2441. format = this.getFormat(); // format not specified -> use the current format
  2442. }
  2443. switch (format.toLowerCase()) {
  2444. case "hex":
  2445. return this.toHEXString();
  2446. break;
  2447. case "hexa":
  2448. return this.toHEXAString();
  2449. break;
  2450. case "rgb":
  2451. return this.toRGBString();
  2452. break;
  2453. case "rgba":
  2454. return this.toRGBAString();
  2455. break;
  2456. }
  2457. return false;
  2458. };
  2459.  
  2460. this.toHEXString = function () {
  2461. return jsc.hexColor(
  2462. this.channels.r,
  2463. this.channels.g,
  2464. this.channels.b
  2465. );
  2466. };
  2467.  
  2468. this.toHEXAString = function () {
  2469. return jsc.hexaColor(
  2470. this.channels.r,
  2471. this.channels.g,
  2472. this.channels.b,
  2473. this.channels.a
  2474. );
  2475. };
  2476.  
  2477. this.toRGBString = function () {
  2478. return jsc.rgbColor(
  2479. this.channels.r,
  2480. this.channels.g,
  2481. this.channels.b
  2482. );
  2483. };
  2484.  
  2485. this.toRGBAString = function () {
  2486. return jsc.rgbaColor(
  2487. this.channels.r,
  2488. this.channels.g,
  2489. this.channels.b,
  2490. this.channels.a
  2491. );
  2492. };
  2493.  
  2494. this.toGrayscale = function () {
  2495. return (
  2496. 0.213 * this.channels.r +
  2497. 0.715 * this.channels.g +
  2498. 0.072 * this.channels.b
  2499. );
  2500. };
  2501.  
  2502. this.toCanvas = function () {
  2503. return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas;
  2504. };
  2505.  
  2506. this.toDataURL = function () {
  2507. return this.toCanvas().toDataURL();
  2508. };
  2509.  
  2510. this.toBackground = function () {
  2511. return jsc.pub.background(this.toRGBAString());
  2512. };
  2513.  
  2514. this.isLight = function () {
  2515. return this.toGrayscale() > 255 / 2;
  2516. };
  2517.  
  2518. this.hide = function () {
  2519. if (isPickerOwner()) {
  2520. detachPicker();
  2521. }
  2522. };
  2523.  
  2524. this.show = function () {
  2525. drawPicker();
  2526. };
  2527.  
  2528. this.redraw = function () {
  2529. if (isPickerOwner()) {
  2530. drawPicker();
  2531. }
  2532. };
  2533.  
  2534. this.getFormat = function () {
  2535. return this._currentFormat;
  2536. };
  2537.  
  2538. this._setFormat = function (format) {
  2539. this._currentFormat = format.toLowerCase();
  2540. };
  2541.  
  2542. this.hasAlphaChannel = function () {
  2543. if (this.alphaChannel === "auto") {
  2544. return (
  2545. this.format.toLowerCase() === "any" || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled
  2546. jsc.isAlphaFormat(this.getFormat()) || // the current format supports alpha channel
  2547. this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel
  2548. this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel
  2549. );
  2550. }
  2551.  
  2552. return this.alphaChannel; // the alpha channel is explicitly set
  2553. };
  2554.  
  2555. this.processValueInput = function (str) {
  2556. if (!this.fromString(str)) {
  2557. // could not parse the color value - let's just expose the current color
  2558. this.exposeColor();
  2559. }
  2560. };
  2561.  
  2562. this.processAlphaInput = function (str) {
  2563. if (!this.fromHSVA(null, null, null, parseFloat(str))) {
  2564. // could not parse the alpha value - let's just expose the current color
  2565. this.exposeColor();
  2566. }
  2567. };
  2568.  
  2569. this.exposeColor = function (flags) {
  2570. var colorStr = this.toString();
  2571. var fmt = this.getFormat();
  2572.  
  2573. // reflect current color in data- attribute
  2574. jsc.setDataAttr(this.targetElement, "current-color", colorStr);
  2575.  
  2576. if (!(flags & jsc.flags.leaveValue) && this.valueElement) {
  2577. if (fmt === "hex" || fmt === "hexa") {
  2578. if (!this.uppercase) {
  2579. colorStr = colorStr.toLowerCase();
  2580. }
  2581. if (!this.hash) {
  2582. colorStr = colorStr.replace(/^#/, "");
  2583. }
  2584. }
  2585. this.setValueElementValue(colorStr);
  2586. }
  2587.  
  2588. if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) {
  2589. var alphaVal = Math.round(this.channels.a * 100) / 100;
  2590. this.setAlphaElementValue(alphaVal);
  2591. }
  2592.  
  2593. if (!(flags & jsc.flags.leavePreview) && this.previewElement) {
  2594. var previewPos = null; // 'left' | 'right' (null -> fill the entire element)
  2595.  
  2596. if (
  2597. jsc.isTextInput(this.previewElement) || // text input
  2598. (jsc.isButton(this.previewElement) &&
  2599. !jsc.isButtonEmpty(this.previewElement)) // button with text
  2600. ) {
  2601. previewPos = this.previewPosition;
  2602. }
  2603.  
  2604. this.setPreviewElementBg(this.toRGBAString());
  2605. }
  2606.  
  2607. if (isPickerOwner()) {
  2608. redrawPad();
  2609. redrawSld();
  2610. redrawASld();
  2611. }
  2612. };
  2613.  
  2614. this.setPreviewElementBg = function (color) {
  2615. if (!this.previewElement) {
  2616. return;
  2617. }
  2618.  
  2619. var position = null; // color preview position: null | 'left' | 'right'
  2620. var width = null; // color preview width: px | null = fill the entire element
  2621. if (
  2622. jsc.isTextInput(this.previewElement) || // text input
  2623. (jsc.isButton(this.previewElement) &&
  2624. !jsc.isButtonEmpty(this.previewElement)) // button with text
  2625. ) {
  2626. position = this.previewPosition;
  2627. width = this.previewSize;
  2628. }
  2629.  
  2630. var backgrounds = [];
  2631.  
  2632. if (!color) {
  2633. // there is no color preview to display -> let's remove any previous background image
  2634. backgrounds.push({
  2635. image: "none",
  2636. position: "left top",
  2637. size: "auto",
  2638. repeat: "no-repeat",
  2639. origin: "padding-box",
  2640. });
  2641. } else {
  2642. // CSS gradient for background color preview
  2643. backgrounds.push({
  2644. image: jsc.genColorPreviewGradient(
  2645. color,
  2646. position,
  2647. width ? width - jsc.pub.previewSeparator.length : null
  2648. ),
  2649. position: "left top",
  2650. size: "auto",
  2651. repeat: position ? "repeat-y" : "repeat",
  2652. origin: "padding-box",
  2653. });
  2654.  
  2655. // data URL of generated PNG image with a gray transparency chessboard
  2656. var preview = jsc.genColorPreviewCanvas(
  2657. "rgba(0,0,0,0)",
  2658. position ? { left: "right", right: "left" }[position] : null,
  2659. width,
  2660. true
  2661. );
  2662. backgrounds.push({
  2663. image: "url('" + preview.canvas.toDataURL() + "')",
  2664. position: (position || "left") + " top",
  2665. size: preview.width + "px " + preview.height + "px",
  2666. repeat: position ? "repeat-y" : "repeat",
  2667. origin: "padding-box",
  2668. });
  2669. }
  2670.  
  2671. var bg = {
  2672. image: [],
  2673. position: [],
  2674. size: [],
  2675. repeat: [],
  2676. origin: [],
  2677. };
  2678. for (var i = 0; i < backgrounds.length; i += 1) {
  2679. bg.image.push(backgrounds[i].image);
  2680. bg.position.push(backgrounds[i].position);
  2681. bg.size.push(backgrounds[i].size);
  2682. bg.repeat.push(backgrounds[i].repeat);
  2683. bg.origin.push(backgrounds[i].origin);
  2684. }
  2685.  
  2686. // set previewElement's background-images
  2687. var sty = {
  2688. "background-image": bg.image.join(", "),
  2689. "background-position": bg.position.join(", "),
  2690. "background-size": bg.size.join(", "),
  2691. "background-repeat": bg.repeat.join(", "),
  2692. "background-origin": bg.origin.join(", "),
  2693. };
  2694. jsc.setStyle(this.previewElement, sty, this.forceStyle);
  2695.  
  2696. // set/restore previewElement's padding
  2697. var padding = {
  2698. left: null,
  2699. right: null,
  2700. };
  2701. if (position) {
  2702. padding[position] = this.previewSize + this.previewPadding + "px";
  2703. }
  2704.  
  2705. var sty = {
  2706. "padding-left": padding.left,
  2707. "padding-right": padding.right,
  2708. };
  2709. jsc.setStyle(this.previewElement, sty, this.forceStyle, true);
  2710. };
  2711.  
  2712. this.setValueElementValue = function (str) {
  2713. if (this.valueElement) {
  2714. if (jsc.nodeName(this.valueElement) === "input") {
  2715. this.valueElement.value = str;
  2716. } else {
  2717. this.valueElement.innerHTML = str;
  2718. }
  2719. }
  2720. };
  2721.  
  2722. this.setAlphaElementValue = function (str) {
  2723. if (this.alphaElement) {
  2724. if (jsc.nodeName(this.alphaElement) === "input") {
  2725. this.alphaElement.value = str;
  2726. } else {
  2727. this.alphaElement.innerHTML = str;
  2728. }
  2729. }
  2730. };
  2731.  
  2732. this._processParentElementsInDOM = function () {
  2733. if (this._parentElementsProcessed) {
  2734. return;
  2735. }
  2736. this._parentElementsProcessed = true;
  2737.  
  2738. var elm = this.targetElement;
  2739. do {
  2740. // If the target element or one of its parent nodes has fixed position,
  2741. // then use fixed positioning instead
  2742. var compStyle = jsc.getCompStyle(elm);
  2743. if (
  2744. compStyle.position &&
  2745. compStyle.position.toLowerCase() === "fixed"
  2746. ) {
  2747. this.fixed = true;
  2748. }
  2749.  
  2750. if (elm !== this.targetElement) {
  2751. // Ensure to attach onParentScroll only once to each parent element
  2752. // (multiple targetElements can share the same parent nodes)
  2753. //
  2754. // Note: It's not just offsetParents that can be scrollable,
  2755. // that's why we loop through all parent nodes
  2756. if (!jsc.getData(elm, "hasScrollListener")) {
  2757. elm.addEventListener("scroll", jsc.onParentScroll, false);
  2758. jsc.setData(elm, "hasScrollListener", true);
  2759. }
  2760. }
  2761. } while ((elm = elm.parentNode) && jsc.nodeName(elm) !== "body");
  2762. };
  2763.  
  2764. this.tryHide = function () {
  2765. if (this.hideOnLeave) {
  2766. this.hide();
  2767. }
  2768. };
  2769.  
  2770. this.set__palette = function (val) {
  2771. this.palette = val;
  2772. this._palette = jsc.parsePaletteValue(val);
  2773. this._paletteHasTransparency = jsc.containsTranparentColor(
  2774. this._palette
  2775. );
  2776. };
  2777.  
  2778. function setOption(option, value) {
  2779. if (typeof option !== "string") {
  2780. throw new Error("Invalid value for option name: " + option);
  2781. }
  2782.  
  2783. // enum option
  2784. if (jsc.enumOpts.hasOwnProperty(option)) {
  2785. if (typeof value === "string") {
  2786. // enum string values are case insensitive
  2787. value = value.toLowerCase();
  2788. }
  2789. if (jsc.enumOpts[option].indexOf(value) === -1) {
  2790. throw new Error(
  2791. "Option '" + option + "' has invalid value: " + value
  2792. );
  2793. }
  2794. }
  2795.  
  2796. // deprecated option
  2797. if (jsc.deprecatedOpts.hasOwnProperty(option)) {
  2798. var oldOpt = option;
  2799. var newOpt = jsc.deprecatedOpts[option];
  2800. if (newOpt) {
  2801. // if we have a new name for this option, let's log a warning and use the new name
  2802. console.warn(
  2803. "Option '%s' is DEPRECATED, using '%s' instead." + jsc.docsRef,
  2804. oldOpt,
  2805. newOpt
  2806. );
  2807. option = newOpt;
  2808. } else {
  2809. // new name not available for the option
  2810. throw new Error("Option '" + option + "' is DEPRECATED");
  2811. }
  2812. }
  2813.  
  2814. var setter = "set__" + option;
  2815.  
  2816. if (typeof THIS[setter] === "function") {
  2817. // a setter exists for this option
  2818. THIS[setter](value);
  2819. return true;
  2820. } else if (option in THIS) {
  2821. // option exists as a property
  2822. THIS[option] = value;
  2823. return true;
  2824. }
  2825.  
  2826. throw new Error("Unrecognized configuration option: " + option);
  2827. }
  2828.  
  2829. function getOption(option) {
  2830. if (typeof option !== "string") {
  2831. throw new Error("Invalid value for option name: " + option);
  2832. }
  2833.  
  2834. // deprecated option
  2835. if (jsc.deprecatedOpts.hasOwnProperty(option)) {
  2836. var oldOpt = option;
  2837. var newOpt = jsc.deprecatedOpts[option];
  2838. if (newOpt) {
  2839. // if we have a new name for this option, let's log a warning and use the new name
  2840. console.warn(
  2841. "Option '%s' is DEPRECATED, using '%s' instead." + jsc.docsRef,
  2842. oldOpt,
  2843. newOpt
  2844. );
  2845. option = newOpt;
  2846. } else {
  2847. // new name not available for the option
  2848. throw new Error("Option '" + option + "' is DEPRECATED");
  2849. }
  2850. }
  2851.  
  2852. var getter = "get__" + option;
  2853.  
  2854. if (typeof THIS[getter] === "function") {
  2855. // a getter exists for this option
  2856. return THIS[getter](value);
  2857. } else if (option in THIS) {
  2858. // option exists as a property
  2859. return THIS[option];
  2860. }
  2861.  
  2862. throw new Error("Unrecognized configuration option: " + option);
  2863. }
  2864.  
  2865. function detachPicker() {
  2866. jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName);
  2867. jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
  2868. delete jsc.picker.owner;
  2869. }
  2870.  
  2871. function drawPicker() {
  2872. // At this point, when drawing the picker, we know what the parent elements are
  2873. // and we can do all related DOM operations, such as registering events on them
  2874. // or checking their positioning
  2875. THIS._processParentElementsInDOM();
  2876.  
  2877. if (!jsc.picker) {
  2878. jsc.picker = {
  2879. owner: null, // owner picker instance
  2880. wrap: jsc.createEl("div"),
  2881. box: jsc.createEl("div"),
  2882. boxS: jsc.createEl("div"), // shadow area
  2883. boxB: jsc.createEl("div"), // border
  2884. pad: jsc.createEl("div"),
  2885. padB: jsc.createEl("div"), // border
  2886. padM: jsc.createEl("div"), // mouse/touch area
  2887. padCanvas: jsc.createPadCanvas(),
  2888. cross: jsc.createEl("div"),
  2889. crossBY: jsc.createEl("div"), // border Y
  2890. crossBX: jsc.createEl("div"), // border X
  2891. crossLY: jsc.createEl("div"), // line Y
  2892. crossLX: jsc.createEl("div"), // line X
  2893. sld: jsc.createEl("div"), // slider
  2894. sldB: jsc.createEl("div"), // border
  2895. sldM: jsc.createEl("div"), // mouse/touch area
  2896. sldGrad: jsc.createSliderGradient(),
  2897. sldPtrS: jsc.createEl("div"), // slider pointer spacer
  2898. sldPtrIB: jsc.createEl("div"), // slider pointer inner border
  2899. sldPtrMB: jsc.createEl("div"), // slider pointer middle border
  2900. sldPtrOB: jsc.createEl("div"), // slider pointer outer border
  2901. asld: jsc.createEl("div"), // alpha slider
  2902. asldB: jsc.createEl("div"), // border
  2903. asldM: jsc.createEl("div"), // mouse/touch area
  2904. asldGrad: jsc.createASliderGradient(),
  2905. asldPtrS: jsc.createEl("div"), // slider pointer spacer
  2906. asldPtrIB: jsc.createEl("div"), // slider pointer inner border
  2907. asldPtrMB: jsc.createEl("div"), // slider pointer middle border
  2908. asldPtrOB: jsc.createEl("div"), // slider pointer outer border
  2909. pal: jsc.createEl("div"), // palette
  2910. btn: jsc.createEl("div"),
  2911. btnT: jsc.createEl("div"), // text
  2912. };
  2913.  
  2914. jsc.picker.pad.appendChild(jsc.picker.padCanvas.elm);
  2915. jsc.picker.padB.appendChild(jsc.picker.pad);
  2916. jsc.picker.cross.appendChild(jsc.picker.crossBY);
  2917. jsc.picker.cross.appendChild(jsc.picker.crossBX);
  2918. jsc.picker.cross.appendChild(jsc.picker.crossLY);
  2919. jsc.picker.cross.appendChild(jsc.picker.crossLX);
  2920. jsc.picker.padB.appendChild(jsc.picker.cross);
  2921. jsc.picker.box.appendChild(jsc.picker.padB);
  2922. jsc.picker.box.appendChild(jsc.picker.padM);
  2923.  
  2924. jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
  2925. jsc.picker.sldB.appendChild(jsc.picker.sld);
  2926. jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
  2927. jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
  2928. jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
  2929. jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
  2930. jsc.picker.box.appendChild(jsc.picker.sldB);
  2931. jsc.picker.box.appendChild(jsc.picker.sldM);
  2932.  
  2933. jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm);
  2934. jsc.picker.asldB.appendChild(jsc.picker.asld);
  2935. jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB);
  2936. jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB);
  2937. jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB);
  2938. jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS);
  2939. jsc.picker.box.appendChild(jsc.picker.asldB);
  2940. jsc.picker.box.appendChild(jsc.picker.asldM);
  2941.  
  2942. jsc.picker.box.appendChild(jsc.picker.pal);
  2943.  
  2944. jsc.picker.btn.appendChild(jsc.picker.btnT);
  2945. jsc.picker.box.appendChild(jsc.picker.btn);
  2946.  
  2947. jsc.picker.boxB.appendChild(jsc.picker.box);
  2948. jsc.picker.wrap.appendChild(jsc.picker.boxS);
  2949. jsc.picker.wrap.appendChild(jsc.picker.boxB);
  2950.  
  2951. jsc.picker.wrap.addEventListener(
  2952. "touchstart",
  2953. jsc.onPickerTouchStart,
  2954. jsc.isPassiveEventSupported ? { passive: false } : false
  2955. );
  2956. }
  2957.  
  2958. var p = jsc.picker;
  2959.  
  2960. var displaySlider = !!jsc.getSliderChannel(THIS);
  2961. var displayAlphaSlider = THIS.hasAlphaChannel();
  2962. var pickerDims = jsc.getPickerDims(THIS);
  2963. var crossOuterSize =
  2964. 2 * THIS.pointerBorderWidth +
  2965. THIS.pointerThickness +
  2966. 2 * THIS.crossSize;
  2967. var controlPadding = jsc.getControlPadding(THIS);
  2968. var borderRadius = Math.min(
  2969. THIS.borderRadius,
  2970. Math.round(THIS.padding * Math.PI)
  2971. ); // px
  2972. var padCursor = "crosshair";
  2973.  
  2974. // wrap
  2975. p.wrap.className = "jscolor-wrap";
  2976. p.wrap.style.width = pickerDims.borderW + "px";
  2977. p.wrap.style.height = pickerDims.borderH + "px";
  2978. p.wrap.style.zIndex = THIS.zIndex;
  2979.  
  2980. // picker
  2981. p.box.className = "jscolor-picker";
  2982. p.box.style.width = pickerDims.paddedW + "px";
  2983. p.box.style.height = pickerDims.paddedH + "px";
  2984.  
  2985. // picker shadow
  2986. p.boxS.className = "jscolor-shadow";
  2987. jsc.setBorderRadius(p.boxS, borderRadius + "px");
  2988.  
  2989. // picker border
  2990. p.boxB.className = "jscolor-border";
  2991. p.boxB.style.border = THIS.borderWidth + "px solid";
  2992. p.boxB.style.borderColor = THIS.borderColor;
  2993. p.boxB.style.background = THIS.backgroundColor;
  2994. jsc.setBorderRadius(p.boxB, borderRadius + "px");
  2995.  
  2996. // IE hack:
  2997. // If the element is transparent, IE will trigger the event on the elements under it,
  2998. // e.g. on Canvas or on elements with border
  2999. p.padM.style.background = "rgba(255,0,0,.2)";
  3000. p.sldM.style.background = "rgba(0,255,0,.2)";
  3001. p.asldM.style.background = "rgba(0,0,255,.2)";
  3002.  
  3003. p.padM.style.opacity =
  3004. p.sldM.style.opacity =
  3005. p.asldM.style.opacity =
  3006. "0";
  3007.  
  3008. // pad
  3009. p.pad.style.position = "relative";
  3010. p.pad.style.width = THIS.width + "px";
  3011. p.pad.style.height = THIS.height + "px";
  3012.  
  3013. // pad - color spectrum (HSV and HVS)
  3014. p.padCanvas.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS));
  3015.  
  3016. // pad border
  3017. p.padB.style.position = "absolute";
  3018. p.padB.style.left = THIS.padding + "px";
  3019. p.padB.style.top = THIS.padding + "px";
  3020. p.padB.style.border = THIS.controlBorderWidth + "px solid";
  3021. p.padB.style.borderColor = THIS.controlBorderColor;
  3022.  
  3023. // pad mouse area
  3024. p.padM.style.position = "absolute";
  3025. p.padM.style.left = 0 + "px";
  3026. p.padM.style.top = 0 + "px";
  3027. p.padM.style.width =
  3028. THIS.padding +
  3029. 2 * THIS.controlBorderWidth +
  3030. THIS.width +
  3031. controlPadding +
  3032. "px";
  3033. p.padM.style.height =
  3034. 2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
  3035. p.padM.style.cursor = padCursor;
  3036. jsc.setData(p.padM, {
  3037. instance: THIS,
  3038. control: "pad",
  3039. });
  3040.  
  3041. // pad cross
  3042. p.cross.style.position = "absolute";
  3043. p.cross.style.left = p.cross.style.top = "0";
  3044. p.cross.style.width = p.cross.style.height = crossOuterSize + "px";
  3045.  
  3046. // pad cross border Y and X
  3047. p.crossBY.style.position = p.crossBX.style.position = "absolute";
  3048. p.crossBY.style.background = p.crossBX.style.background =
  3049. THIS.pointerBorderColor;
  3050. p.crossBY.style.width = p.crossBX.style.height =
  3051. 2 * THIS.pointerBorderWidth + THIS.pointerThickness + "px";
  3052. p.crossBY.style.height = p.crossBX.style.width =
  3053. crossOuterSize + "px";
  3054. p.crossBY.style.left = p.crossBX.style.top =
  3055. Math.floor(crossOuterSize / 2) -
  3056. Math.floor(THIS.pointerThickness / 2) -
  3057. THIS.pointerBorderWidth +
  3058. "px";
  3059. p.crossBY.style.top = p.crossBX.style.left = "0";
  3060.  
  3061. // pad cross line Y and X
  3062. p.crossLY.style.position = p.crossLX.style.position = "absolute";
  3063. p.crossLY.style.background = p.crossLX.style.background =
  3064. THIS.pointerColor;
  3065. p.crossLY.style.height = p.crossLX.style.width =
  3066. crossOuterSize - 2 * THIS.pointerBorderWidth + "px";
  3067. p.crossLY.style.width = p.crossLX.style.height =
  3068. THIS.pointerThickness + "px";
  3069. p.crossLY.style.left = p.crossLX.style.top =
  3070. Math.floor(crossOuterSize / 2) -
  3071. Math.floor(THIS.pointerThickness / 2) +
  3072. "px";
  3073. p.crossLY.style.top = p.crossLX.style.left =
  3074. THIS.pointerBorderWidth + "px";
  3075.  
  3076. // slider
  3077. p.sld.style.overflow = "hidden";
  3078. p.sld.style.width = THIS.sliderSize + "px";
  3079. p.sld.style.height = THIS.height + "px";
  3080.  
  3081. // slider gradient
  3082. p.sldGrad.draw(THIS.sliderSize, THIS.height, "#000", "#000");
  3083.  
  3084. // slider border
  3085. p.sldB.style.display = displaySlider ? "block" : "none";
  3086. p.sldB.style.position = "absolute";
  3087. p.sldB.style.left =
  3088. THIS.padding +
  3089. THIS.width +
  3090. 2 * THIS.controlBorderWidth +
  3091. 2 * controlPadding +
  3092. "px";
  3093. p.sldB.style.top = THIS.padding + "px";
  3094. p.sldB.style.border = THIS.controlBorderWidth + "px solid";
  3095. p.sldB.style.borderColor = THIS.controlBorderColor;
  3096.  
  3097. // slider mouse area
  3098. p.sldM.style.display = displaySlider ? "block" : "none";
  3099. p.sldM.style.position = "absolute";
  3100. p.sldM.style.left =
  3101. THIS.padding +
  3102. THIS.width +
  3103. 2 * THIS.controlBorderWidth +
  3104. controlPadding +
  3105. "px";
  3106. p.sldM.style.top = 0 + "px";
  3107. p.sldM.style.width =
  3108. THIS.sliderSize +
  3109. 2 * controlPadding +
  3110. 2 * THIS.controlBorderWidth +
  3111. (displayAlphaSlider
  3112. ? 0
  3113. : Math.max(0, THIS.padding - controlPadding)) + // remaining padding to the right edge
  3114. "px";
  3115. p.sldM.style.height =
  3116. 2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
  3117. p.sldM.style.cursor = "default";
  3118. jsc.setData(p.sldM, {
  3119. instance: THIS,
  3120. control: "sld",
  3121. });
  3122.  
  3123. // slider pointer inner and outer border
  3124. p.sldPtrIB.style.border = p.sldPtrOB.style.border =
  3125. THIS.pointerBorderWidth + "px solid " + THIS.pointerBorderColor;
  3126.  
  3127. // slider pointer outer border
  3128. p.sldPtrOB.style.position = "absolute";
  3129. p.sldPtrOB.style.left =
  3130. -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + "px";
  3131. p.sldPtrOB.style.top = "0";
  3132.  
  3133. // slider pointer middle border
  3134. p.sldPtrMB.style.border =
  3135. THIS.pointerThickness + "px solid " + THIS.pointerColor;
  3136.  
  3137. // slider pointer spacer
  3138. p.sldPtrS.style.width = THIS.sliderSize + "px";
  3139. p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + "px";
  3140.  
  3141. // alpha slider
  3142. p.asld.style.overflow = "hidden";
  3143. p.asld.style.width = THIS.sliderSize + "px";
  3144. p.asld.style.height = THIS.height + "px";
  3145.  
  3146. // alpha slider gradient
  3147. p.asldGrad.draw(THIS.sliderSize, THIS.height, "#000");
  3148.  
  3149. // alpha slider border
  3150. p.asldB.style.display = displayAlphaSlider ? "block" : "none";
  3151. p.asldB.style.position = "absolute";
  3152. p.asldB.style.left =
  3153. THIS.padding +
  3154. THIS.width +
  3155. 2 * THIS.controlBorderWidth +
  3156. controlPadding +
  3157. (displaySlider
  3158. ? THIS.sliderSize +
  3159. 3 * controlPadding +
  3160. 2 * THIS.controlBorderWidth
  3161. : 0) +
  3162. "px";
  3163. p.asldB.style.top = THIS.padding + "px";
  3164. p.asldB.style.border = THIS.controlBorderWidth + "px solid";
  3165. p.asldB.style.borderColor = THIS.controlBorderColor;
  3166.  
  3167. // alpha slider mouse area
  3168. p.asldM.style.display = displayAlphaSlider ? "block" : "none";
  3169. p.asldM.style.position = "absolute";
  3170. p.asldM.style.left =
  3171. THIS.padding +
  3172. THIS.width +
  3173. 2 * THIS.controlBorderWidth +
  3174. controlPadding +
  3175. (displaySlider
  3176. ? THIS.sliderSize +
  3177. 2 * controlPadding +
  3178. 2 * THIS.controlBorderWidth
  3179. : 0) +
  3180. "px";
  3181. p.asldM.style.top = 0 + "px";
  3182. p.asldM.style.width =
  3183. THIS.sliderSize +
  3184. 2 * controlPadding +
  3185. 2 * THIS.controlBorderWidth +
  3186. Math.max(0, THIS.padding - controlPadding) + // remaining padding to the right edge
  3187. "px";
  3188. p.asldM.style.height =
  3189. 2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
  3190. p.asldM.style.cursor = "default";
  3191. jsc.setData(p.asldM, {
  3192. instance: THIS,
  3193. control: "asld",
  3194. });
  3195.  
  3196. // alpha slider pointer inner and outer border
  3197. p.asldPtrIB.style.border = p.asldPtrOB.style.border =
  3198. THIS.pointerBorderWidth + "px solid " + THIS.pointerBorderColor;
  3199.  
  3200. // alpha slider pointer outer border
  3201. p.asldPtrOB.style.position = "absolute";
  3202. p.asldPtrOB.style.left =
  3203. -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + "px";
  3204. p.asldPtrOB.style.top = "0";
  3205.  
  3206. // alpha slider pointer middle border
  3207. p.asldPtrMB.style.border =
  3208. THIS.pointerThickness + "px solid " + THIS.pointerColor;
  3209.  
  3210. // alpha slider pointer spacer
  3211. p.asldPtrS.style.width = THIS.sliderSize + "px";
  3212. p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + "px";
  3213.  
  3214. // palette
  3215. p.pal.className = "jscolor-palette";
  3216. p.pal.style.display = pickerDims.palette.rows ? "block" : "none";
  3217. p.pal.style.left = THIS.padding + "px";
  3218. p.pal.style.top =
  3219. 2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
  3220.  
  3221. // palette's color samples
  3222.  
  3223. p.pal.innerHTML = "";
  3224.  
  3225. var chessboard = jsc.genColorPreviewCanvas("rgba(0,0,0,0)");
  3226.  
  3227. var si = 0; // color sample's index
  3228. for (var r = 0; r < pickerDims.palette.rows; r++) {
  3229. for (
  3230. var c = 0;
  3231. c < pickerDims.palette.cols && si < THIS._palette.length;
  3232. c++, si++
  3233. ) {
  3234. var sampleColor = THIS._palette[si];
  3235. var sampleCssColor = jsc.rgbaColor.apply(null, sampleColor.rgba);
  3236.  
  3237. var sc = jsc.createEl("div"); // color sample's color
  3238. sc.style.width =
  3239. pickerDims.palette.cellW - 2 * THIS.controlBorderWidth + "px";
  3240. sc.style.height =
  3241. pickerDims.palette.cellH - 2 * THIS.controlBorderWidth + "px";
  3242. sc.style.backgroundColor = sampleCssColor;
  3243.  
  3244. var sw = jsc.createEl("div"); // color sample's wrap
  3245. sw.className = "jscolor-palette-sw";
  3246. sw.style.left =
  3247. (pickerDims.palette.cols <= 1
  3248. ? 0
  3249. : Math.round(
  3250. 10 *
  3251. (c *
  3252. ((pickerDims.contentW - pickerDims.palette.cellW) /
  3253. (pickerDims.palette.cols - 1)))
  3254. ) / 10) + "px";
  3255. sw.style.top =
  3256. r * (pickerDims.palette.cellH + THIS.paletteSpacing) + "px";
  3257. sw.style.border = THIS.controlBorderWidth + "px solid";
  3258. sw.style.borderColor = THIS.controlBorderColor;
  3259. if (sampleColor.rgba[3] !== null && sampleColor.rgba[3] < 1.0) {
  3260. // only create chessboard background if the sample has transparency
  3261. sw.style.backgroundImage =
  3262. "url('" + chessboard.canvas.toDataURL() + "')";
  3263. sw.style.backgroundRepeat = "repeat";
  3264. sw.style.backgroundPosition = "center center";
  3265. }
  3266. jsc.setData(sw, {
  3267. instance: THIS,
  3268. control: "palette-sw",
  3269. color: sampleColor,
  3270. });
  3271. sw.addEventListener("click", jsc.onPaletteSampleClick, false);
  3272. sw.appendChild(sc);
  3273. p.pal.appendChild(sw);
  3274. }
  3275. }
  3276.  
  3277. // the Close button
  3278. function setBtnBorder() {
  3279. var insetColors = THIS.controlBorderColor.split(/\s+/);
  3280. var outsetColor =
  3281. insetColors.length < 2
  3282. ? insetColors[0]
  3283. : insetColors[1] +
  3284. " " +
  3285. insetColors[0] +
  3286. " " +
  3287. insetColors[0] +
  3288. " " +
  3289. insetColors[1];
  3290. p.btn.style.borderColor = outsetColor;
  3291. }
  3292. var btnPadding = 15; // px
  3293. p.btn.className = "jscolor-btn jscolor-btn-close";
  3294. p.btn.style.display = THIS.closeButton ? "block" : "none";
  3295. p.btn.style.left = THIS.padding + "px";
  3296. p.btn.style.bottom = THIS.padding + "px";
  3297. p.btn.style.padding = "0 " + btnPadding + "px";
  3298. p.btn.style.maxWidth =
  3299. pickerDims.contentW -
  3300. 2 * THIS.controlBorderWidth -
  3301. 2 * btnPadding +
  3302. "px";
  3303. p.btn.style.height = THIS.buttonHeight + "px";
  3304. p.btn.style.border = THIS.controlBorderWidth + "px solid";
  3305. setBtnBorder();
  3306. p.btn.style.color = THIS.buttonColor;
  3307. p.btn.onmousedown = function () {
  3308. THIS.hide();
  3309. };
  3310. p.btnT.style.display = "inline";
  3311. p.btnT.style.lineHeight = THIS.buttonHeight + "px";
  3312. p.btnT.innerText = THIS.closeText;
  3313.  
  3314. // reposition the pointers
  3315. redrawPad();
  3316. redrawSld();
  3317. redrawASld();
  3318.  
  3319. // If we are changing the owner without first closing the picker,
  3320. // make sure to first deal with the old owner
  3321. if (jsc.picker.owner && jsc.picker.owner !== THIS) {
  3322. jsc.removeClass(
  3323. jsc.picker.owner.targetElement,
  3324. jsc.pub.activeClassName
  3325. );
  3326. }
  3327.  
  3328. // Set a new picker owner
  3329. jsc.picker.owner = THIS;
  3330.  
  3331. // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
  3332. // after setting the owner
  3333. jsc.redrawPosition();
  3334.  
  3335. if (p.wrap.parentNode !== THIS.container) {
  3336. THIS.container.appendChild(p.wrap);
  3337. }
  3338.  
  3339. jsc.addClass(THIS.targetElement, jsc.pub.activeClassName);
  3340. }
  3341.  
  3342. function redrawPad() {
  3343. // redraw the pad pointer
  3344. var yChannel = jsc.getPadYChannel(THIS);
  3345. var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1));
  3346. var y = Math.round(
  3347. (1 - THIS.channels[yChannel] / 100) * (THIS.height - 1)
  3348. );
  3349. var crossOuterSize =
  3350. 2 * THIS.pointerBorderWidth +
  3351. THIS.pointerThickness +
  3352. 2 * THIS.crossSize;
  3353. var ofs = -Math.floor(crossOuterSize / 2);
  3354. jsc.picker.cross.style.left = x + ofs + "px";
  3355. jsc.picker.cross.style.top = y + ofs + "px";
  3356.  
  3357. // redraw the slider
  3358. switch (jsc.getSliderChannel(THIS)) {
  3359. case "s":
  3360. var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v);
  3361. var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v);
  3362. var color1 =
  3363. "rgb(" +
  3364. Math.round(rgb1[0]) +
  3365. "," +
  3366. Math.round(rgb1[1]) +
  3367. "," +
  3368. Math.round(rgb1[2]) +
  3369. ")";
  3370. var color2 =
  3371. "rgb(" +
  3372. Math.round(rgb2[0]) +
  3373. "," +
  3374. Math.round(rgb2[1]) +
  3375. "," +
  3376. Math.round(rgb2[2]) +
  3377. ")";
  3378. jsc.picker.sldGrad.draw(
  3379. THIS.sliderSize,
  3380. THIS.height,
  3381. color1,
  3382. color2
  3383. );
  3384. break;
  3385. case "v":
  3386. var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100);
  3387. var color1 =
  3388. "rgb(" +
  3389. Math.round(rgb[0]) +
  3390. "," +
  3391. Math.round(rgb[1]) +
  3392. "," +
  3393. Math.round(rgb[2]) +
  3394. ")";
  3395. var color2 = "#000";
  3396. jsc.picker.sldGrad.draw(
  3397. THIS.sliderSize,
  3398. THIS.height,
  3399. color1,
  3400. color2
  3401. );
  3402. break;
  3403. }
  3404.  
  3405. // redraw the alpha slider
  3406. jsc.picker.asldGrad.draw(
  3407. THIS.sliderSize,
  3408. THIS.height,
  3409. THIS.toHEXString()
  3410. );
  3411. }
  3412.  
  3413. function redrawSld() {
  3414. var sldChannel = jsc.getSliderChannel(THIS);
  3415. if (sldChannel) {
  3416. // redraw the slider pointer
  3417. var y = Math.round(
  3418. (1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1)
  3419. );
  3420. jsc.picker.sldPtrOB.style.top =
  3421. y -
  3422. (2 * THIS.pointerBorderWidth + THIS.pointerThickness) -
  3423. Math.floor(jsc.pub.sliderInnerSpace / 2) +
  3424. "px";
  3425. }
  3426.  
  3427. // redraw the alpha slider
  3428. jsc.picker.asldGrad.draw(
  3429. THIS.sliderSize,
  3430. THIS.height,
  3431. THIS.toHEXString()
  3432. );
  3433. }
  3434.  
  3435. function redrawASld() {
  3436. var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1));
  3437. jsc.picker.asldPtrOB.style.top =
  3438. y -
  3439. (2 * THIS.pointerBorderWidth + THIS.pointerThickness) -
  3440. Math.floor(jsc.pub.sliderInnerSpace / 2) +
  3441. "px";
  3442. }
  3443.  
  3444. function isPickerOwner() {
  3445. return jsc.picker && jsc.picker.owner === THIS;
  3446. }
  3447.  
  3448. function onValueKeyDown(ev) {
  3449. if (jsc.eventKey(ev) === "Enter") {
  3450. if (THIS.valueElement) {
  3451. THIS.processValueInput(THIS.valueElement.value);
  3452. }
  3453. THIS.tryHide();
  3454. }
  3455. }
  3456.  
  3457. function onAlphaKeyDown(ev) {
  3458. if (jsc.eventKey(ev) === "Enter") {
  3459. if (THIS.alphaElement) {
  3460. THIS.processAlphaInput(THIS.alphaElement.value);
  3461. }
  3462. THIS.tryHide();
  3463. }
  3464. }
  3465.  
  3466. function onValueChange(ev) {
  3467. if (jsc.getData(ev, "internal")) {
  3468. return; // skip if the event was internally triggered by jscolor
  3469. }
  3470.  
  3471. var oldVal = THIS.valueElement.value;
  3472.  
  3473. THIS.processValueInput(THIS.valueElement.value); // this might change the value
  3474.  
  3475. jsc.triggerCallback(THIS, "onChange");
  3476.  
  3477. if (THIS.valueElement.value !== oldVal) {
  3478. // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
  3479. jsc.triggerInputEvent(THIS.valueElement, "change", true, true);
  3480. }
  3481. }
  3482.  
  3483. function onAlphaChange(ev) {
  3484. if (jsc.getData(ev, "internal")) {
  3485. return; // skip if the event was internally triggered by jscolor
  3486. }
  3487.  
  3488. var oldVal = THIS.alphaElement.value;
  3489.  
  3490. THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value
  3491.  
  3492. jsc.triggerCallback(THIS, "onChange");
  3493.  
  3494. // triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format)
  3495. jsc.triggerInputEvent(THIS.valueElement, "change", true, true);
  3496.  
  3497. if (THIS.alphaElement.value !== oldVal) {
  3498. // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
  3499. jsc.triggerInputEvent(THIS.alphaElement, "change", true, true);
  3500. }
  3501. }
  3502.  
  3503. function onValueInput(ev) {
  3504. if (jsc.getData(ev, "internal")) {
  3505. return; // skip if the event was internally triggered by jscolor
  3506. }
  3507.  
  3508. if (THIS.valueElement) {
  3509. THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue);
  3510. }
  3511.  
  3512. jsc.triggerCallback(THIS, "onInput");
  3513.  
  3514. // triggering valueElement's onInput
  3515. // (not needed, it was dispatched normally by the browser)
  3516. }
  3517.  
  3518. function onAlphaInput(ev) {
  3519. if (jsc.getData(ev, "internal")) {
  3520. return; // skip if the event was internally triggered by jscolor
  3521. }
  3522.  
  3523. if (THIS.alphaElement) {
  3524. THIS.fromHSVA(
  3525. null,
  3526. null,
  3527. null,
  3528. parseFloat(THIS.alphaElement.value),
  3529. jsc.flags.leaveAlpha
  3530. );
  3531. }
  3532.  
  3533. jsc.triggerCallback(THIS, "onInput");
  3534.  
  3535. // triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format)
  3536. jsc.triggerInputEvent(THIS.valueElement, "input", true, true);
  3537. }
  3538.  
  3539. // let's process the DEPRECATED 'options' property (this will be later removed)
  3540. if (jsc.pub.options) {
  3541. // let's set custom default options, if specified
  3542. for (var opt in jsc.pub.options) {
  3543. if (jsc.pub.options.hasOwnProperty(opt)) {
  3544. try {
  3545. setOption(opt, jsc.pub.options[opt]);
  3546. } catch (e) {
  3547. console.warn(e);
  3548. }
  3549. }
  3550. }
  3551. }
  3552.  
  3553. // let's apply configuration presets
  3554. //
  3555. var presetsArr = [];
  3556.  
  3557. if (opts.preset) {
  3558. if (typeof opts.preset === "string") {
  3559. presetsArr = opts.preset.split(/\s+/);
  3560. } else if (Array.isArray(opts.preset)) {
  3561. presetsArr = opts.preset.slice(); // slice() to clone
  3562. } else {
  3563. console.warn("Unrecognized preset value");
  3564. }
  3565. }
  3566.  
  3567. // always use the 'default' preset. If it's not listed, append it to the end.
  3568. if (presetsArr.indexOf("default") === -1) {
  3569. presetsArr.push("default");
  3570. }
  3571.  
  3572. // let's apply the presets in reverse order, so that should there be any overlapping options,
  3573. // the formerly listed preset will override the latter
  3574. for (var i = presetsArr.length - 1; i >= 0; i -= 1) {
  3575. var pres = presetsArr[i];
  3576. if (!pres) {
  3577. continue; // preset is empty string
  3578. }
  3579. if (!jsc.pub.presets.hasOwnProperty(pres)) {
  3580. console.warn("Unknown preset: %s", pres);
  3581. continue;
  3582. }
  3583. for (var opt in jsc.pub.presets[pres]) {
  3584. if (jsc.pub.presets[pres].hasOwnProperty(opt)) {
  3585. try {
  3586. setOption(opt, jsc.pub.presets[pres][opt]);
  3587. } catch (e) {
  3588. console.warn(e);
  3589. }
  3590. }
  3591. }
  3592. }
  3593.  
  3594. // let's set specific options for this color picker
  3595. var nonProperties = [
  3596. // these options won't be set as instance properties
  3597. "preset",
  3598. ];
  3599. for (var opt in opts) {
  3600. if (opts.hasOwnProperty(opt)) {
  3601. if (nonProperties.indexOf(opt) === -1) {
  3602. try {
  3603. setOption(opt, opts[opt]);
  3604. } catch (e) {
  3605. console.warn(e);
  3606. }
  3607. }
  3608. }
  3609. }
  3610.  
  3611. //
  3612. // Install the color picker on chosen element(s)
  3613. //
  3614.  
  3615. // Determine picker's container element
  3616. if (this.container === undefined) {
  3617. this.container = window.document.body; // default container is BODY element
  3618. } else {
  3619. // explicitly set to custom element
  3620. this.container = jsc.node(this.container);
  3621. }
  3622.  
  3623. if (!this.container) {
  3624. throw new Error(
  3625. "Cannot instantiate color picker without a container element"
  3626. );
  3627. }
  3628.  
  3629. // Fetch the target element
  3630. this.targetElement = jsc.node(targetElement);
  3631.  
  3632. if (!this.targetElement) {
  3633. // temporarily customized error message to help with migrating from versions prior to 2.2
  3634. if (
  3635. typeof targetElement === "string" &&
  3636. /^[a-zA-Z][\w:.-]*$/.test(targetElement)
  3637. ) {
  3638. // targetElement looks like valid ID
  3639. var possiblyId = targetElement;
  3640. throw new Error(
  3641. "If '" +
  3642. possiblyId +
  3643. "' is supposed to be an ID, please use '#" +
  3644. possiblyId +
  3645. "' or any valid CSS selector."
  3646. );
  3647. }
  3648.  
  3649. throw new Error(
  3650. "Cannot instantiate color picker without a target element"
  3651. );
  3652. }
  3653.  
  3654. if (
  3655. this.targetElement.jscolor &&
  3656. this.targetElement.jscolor instanceof jsc.pub
  3657. ) {
  3658. throw new Error("Color picker already installed on this element");
  3659. }
  3660.  
  3661. // link this instance with the target element
  3662. this.targetElement.jscolor = this;
  3663. jsc.addClass(this.targetElement, jsc.pub.className);
  3664.  
  3665. // register this instance
  3666. jsc.instances.push(this);
  3667.  
  3668. // if target is BUTTON
  3669. if (jsc.isButton(this.targetElement)) {
  3670. if (this.targetElement.type.toLowerCase() !== "button") {
  3671. // on buttons, always force type to be 'button', e.g. in situations the target <button> has no type
  3672. // and thus defaults to 'submit' and would submit the form when clicked
  3673. this.targetElement.type = "button";
  3674. }
  3675.  
  3676. if (jsc.isButtonEmpty(this.targetElement)) {
  3677. // empty button
  3678. // it is important to clear element's contents first.
  3679. // if we're re-instantiating color pickers on DOM that has been modified by changing page's innerHTML,
  3680. // we would keep adding more non-breaking spaces to element's content (because element's contents survive
  3681. // innerHTML changes, but picker instances don't)
  3682. jsc.removeChildren(this.targetElement);
  3683.  
  3684. // let's insert a non-breaking space
  3685. this.targetElement.appendChild(
  3686. window.document.createTextNode("\xa0")
  3687. );
  3688.  
  3689. // set min-width = previewSize, if not already greater
  3690. var compStyle = jsc.getCompStyle(this.targetElement);
  3691. var currMinWidth = parseFloat(compStyle["min-width"]) || 0;
  3692. if (currMinWidth < this.previewSize) {
  3693. jsc.setStyle(
  3694. this.targetElement,
  3695. {
  3696. "min-width": this.previewSize + "px",
  3697. },
  3698. this.forceStyle
  3699. );
  3700. }
  3701. }
  3702. }
  3703.  
  3704. // Determine the value element
  3705. if (this.valueElement === undefined) {
  3706. if (jsc.isTextInput(this.targetElement)) {
  3707. // for text inputs, default valueElement is targetElement
  3708. this.valueElement = this.targetElement;
  3709. } else {
  3710. // leave it undefined
  3711. }
  3712. } else if (this.valueElement === null) {
  3713. // explicitly set to null
  3714. // leave it null
  3715. } else {
  3716. // explicitly set to custom element
  3717. this.valueElement = jsc.node(this.valueElement);
  3718. }
  3719.  
  3720. // Determine the alpha element
  3721. if (this.alphaElement) {
  3722. this.alphaElement = jsc.node(this.alphaElement);
  3723. }
  3724.  
  3725. // Determine the preview element
  3726. if (this.previewElement === undefined) {
  3727. this.previewElement = this.targetElement; // default previewElement is targetElement
  3728. } else if (this.previewElement === null) {
  3729. // explicitly set to null
  3730. // leave it null
  3731. } else {
  3732. // explicitly set to custom element
  3733. this.previewElement = jsc.node(this.previewElement);
  3734. }
  3735.  
  3736. // valueElement
  3737. if (this.valueElement && jsc.isTextInput(this.valueElement)) {
  3738. // If the value element has onInput event already set, we need to detach it and attach AFTER our listener.
  3739. // otherwise the picker instance would still contain the old color when accessed from the onInput handler.
  3740. var valueElementOrigEvents = {
  3741. onInput: this.valueElement.oninput,
  3742. };
  3743. this.valueElement.oninput = null;
  3744.  
  3745. this.valueElement.addEventListener("keydown", onValueKeyDown, false);
  3746. this.valueElement.addEventListener("change", onValueChange, false);
  3747. this.valueElement.addEventListener("input", onValueInput, false);
  3748. // the original event listener must be attached AFTER our handler (to let it first set picker's color)
  3749. if (valueElementOrigEvents.onInput) {
  3750. this.valueElement.addEventListener(
  3751. "input",
  3752. valueElementOrigEvents.onInput,
  3753. false
  3754. );
  3755. }
  3756.  
  3757. this.valueElement.setAttribute("autocomplete", "off");
  3758. this.valueElement.setAttribute("autocorrect", "off");
  3759. this.valueElement.setAttribute("autocapitalize", "off");
  3760. this.valueElement.setAttribute("spellcheck", false);
  3761. }
  3762.  
  3763. // alphaElement
  3764. if (this.alphaElement && jsc.isTextInput(this.alphaElement)) {
  3765. this.alphaElement.addEventListener("keydown", onAlphaKeyDown, false);
  3766. this.alphaElement.addEventListener("change", onAlphaChange, false);
  3767. this.alphaElement.addEventListener("input", onAlphaInput, false);
  3768.  
  3769. this.alphaElement.setAttribute("autocomplete", "off");
  3770. this.alphaElement.setAttribute("autocorrect", "off");
  3771. this.alphaElement.setAttribute("autocapitalize", "off");
  3772. this.alphaElement.setAttribute("spellcheck", false);
  3773. }
  3774.  
  3775. // determine initial color value
  3776. //
  3777. var initValue = "FFFFFF";
  3778.  
  3779. if (this.value !== undefined) {
  3780. initValue = this.value; // get initial color from the 'value' property
  3781. } else if (this.valueElement && this.valueElement.value !== undefined) {
  3782. initValue = this.valueElement.value; // get initial color from valueElement's value
  3783. }
  3784.  
  3785. // determine initial alpha value
  3786. //
  3787. var initAlpha = undefined;
  3788.  
  3789. if (this.alpha !== undefined) {
  3790. initAlpha = "" + this.alpha; // get initial alpha value from the 'alpha' property
  3791. } else if (this.alphaElement && this.alphaElement.value !== undefined) {
  3792. initAlpha = this.alphaElement.value; // get initial color from alphaElement's value
  3793. }
  3794.  
  3795. // determine current format based on the initial color value
  3796. //
  3797. this._currentFormat = null;
  3798.  
  3799. if (["auto", "any"].indexOf(this.format.toLowerCase()) > -1) {
  3800. // format is 'auto' or 'any' -> let's auto-detect current format
  3801. var color = jsc.parseColorString(initValue);
  3802. this._currentFormat = color ? color.format : "hex";
  3803. } else {
  3804. // format is specified
  3805. this._currentFormat = this.format.toLowerCase();
  3806. }
  3807.  
  3808. // let's parse the initial color value and expose color's preview
  3809. this.processValueInput(initValue);
  3810.  
  3811. // let's also parse and expose the initial alpha value, if any
  3812. //
  3813. // Note: If the initial color value contains alpha value in it (e.g. in rgba format),
  3814. // this will overwrite it. So we should only process alpha input if there was initial
  3815. // alpha explicitly set, otherwise we could needlessly lose initial value's alpha
  3816. if (initAlpha !== undefined) {
  3817. this.processAlphaInput(initAlpha);
  3818. }
  3819.  
  3820. if (this.random) {
  3821. // randomize the initial color value
  3822. this.randomize.apply(
  3823. this,
  3824. Array.isArray(this.random) ? this.random : []
  3825. );
  3826. }
  3827. },
  3828. };
  3829.  
  3830. //================================
  3831. // Public properties and methods
  3832. //================================
  3833.  
  3834. //
  3835. // These will be publicly available via jscolor.<name> and JSColor.<name>
  3836. //
  3837.  
  3838. // class that will be set to elements having jscolor installed on them
  3839. jsc.pub.className = "jscolor";
  3840.  
  3841. // class that will be set to elements having jscolor active on them
  3842. jsc.pub.activeClassName = "jscolor-active";
  3843.  
  3844. // whether to try to parse the options string by evaluating it using 'new Function()'
  3845. // in case it could not be parsed with JSON.parse()
  3846. jsc.pub.looseJSON = true;
  3847.  
  3848. // presets
  3849. jsc.pub.presets = {};
  3850.  
  3851. // built-in presets
  3852. jsc.pub.presets["default"] = {}; // baseline for customization
  3853.  
  3854. jsc.pub.presets["light"] = {
  3855. // default color scheme
  3856. backgroundColor: "rgba(255,255,255,1)",
  3857. controlBorderColor: "rgba(187,187,187,1)",
  3858. buttonColor: "rgba(0,0,0,1)",
  3859. };
  3860. jsc.pub.presets["dark"] = {
  3861. backgroundColor: "rgba(51,51,51,1)",
  3862. controlBorderColor: "rgba(153,153,153,1)",
  3863. buttonColor: "rgba(240,240,240,1)",
  3864. };
  3865.  
  3866. jsc.pub.presets["small"] = {
  3867. width: 101,
  3868. height: 101,
  3869. padding: 10,
  3870. sliderSize: 14,
  3871. paletteCols: 8,
  3872. };
  3873. jsc.pub.presets["medium"] = {
  3874. width: 181,
  3875. height: 101,
  3876. padding: 12,
  3877. sliderSize: 16,
  3878. paletteCols: 10,
  3879. }; // default size
  3880. jsc.pub.presets["large"] = {
  3881. width: 271,
  3882. height: 151,
  3883. padding: 12,
  3884. sliderSize: 24,
  3885. paletteCols: 15,
  3886. };
  3887.  
  3888. jsc.pub.presets["thin"] = {
  3889. borderWidth: 1,
  3890. controlBorderWidth: 1,
  3891. pointerBorderWidth: 1,
  3892. }; // default thickness
  3893. jsc.pub.presets["thick"] = {
  3894. borderWidth: 2,
  3895. controlBorderWidth: 2,
  3896. pointerBorderWidth: 2,
  3897. };
  3898.  
  3899. // size of space in the sliders
  3900. jsc.pub.sliderInnerSpace = 3; // px
  3901.  
  3902. // transparency chessboard
  3903. jsc.pub.chessboardSize = 8; // px
  3904. jsc.pub.chessboardColor1 = "#666666";
  3905. jsc.pub.chessboardColor2 = "#999999";
  3906.  
  3907. // preview separator
  3908. jsc.pub.previewSeparator = [
  3909. "rgba(255,255,255,.65)",
  3910. "rgba(128,128,128,.65)",
  3911. ];
  3912.  
  3913. // Initializes jscolor
  3914. jsc.pub.init = function () {
  3915. if (jsc.initialized) {
  3916. return;
  3917. }
  3918.  
  3919. // attach some necessary handlers
  3920. window.document.addEventListener(
  3921. "mousedown",
  3922. jsc.onDocumentMouseDown,
  3923. false
  3924. );
  3925. window.document.addEventListener("keyup", jsc.onDocumentKeyUp, false);
  3926. window.addEventListener("resize", jsc.onWindowResize, false);
  3927. window.addEventListener("scroll", jsc.onWindowScroll, false);
  3928.  
  3929. // append default CSS to HEAD
  3930. jsc.appendDefaultCss();
  3931.  
  3932. // install jscolor on current DOM
  3933. jsc.pub.install();
  3934.  
  3935. jsc.initialized = true;
  3936.  
  3937. // call functions waiting in the queue
  3938. while (jsc.readyQueue.length) {
  3939. var func = jsc.readyQueue.shift();
  3940. func();
  3941. }
  3942. };
  3943.  
  3944. // Installs jscolor on current DOM tree
  3945. jsc.pub.install = function (rootNode) {
  3946. var success = true;
  3947.  
  3948. try {
  3949. jsc.installBySelector("[data-jscolor]", rootNode);
  3950. } catch (e) {
  3951. success = false;
  3952. console.warn(e);
  3953. }
  3954.  
  3955. // for backward compatibility with DEPRECATED installation using class name
  3956. if (jsc.pub.lookupClass) {
  3957. try {
  3958. jsc.installBySelector(
  3959. "input." +
  3960. jsc.pub.lookupClass +
  3961. ", " +
  3962. "button." +
  3963. jsc.pub.lookupClass,
  3964. rootNode
  3965. );
  3966. } catch (e) {}
  3967. }
  3968.  
  3969. return success;
  3970. };
  3971.  
  3972. // Registers function to be called as soon as jscolor is initialized (or immediately, if it already is).
  3973. //
  3974. jsc.pub.ready = function (func) {
  3975. if (typeof func !== "function") {
  3976. console.warn("Passed value is not a function");
  3977. return false;
  3978. }
  3979.  
  3980. if (jsc.initialized) {
  3981. func();
  3982. } else {
  3983. jsc.readyQueue.push(func);
  3984. }
  3985. return true;
  3986. };
  3987.  
  3988. // Triggers given input event(s) (e.g. 'input' or 'change') on all color pickers.
  3989. //
  3990. // It is possible to specify multiple events separated with a space.
  3991. // If called before jscolor is initialized, then the events will be triggered after initialization.
  3992. //
  3993. jsc.pub.trigger = function (eventNames) {
  3994. var triggerNow = function () {
  3995. jsc.triggerGlobal(eventNames);
  3996. };
  3997.  
  3998. if (jsc.initialized) {
  3999. triggerNow();
  4000. } else {
  4001. jsc.pub.ready(triggerNow);
  4002. }
  4003. };
  4004.  
  4005. // Hides current color picker box
  4006. jsc.pub.hide = function () {
  4007. if (jsc.picker && jsc.picker.owner) {
  4008. jsc.picker.owner.hide();
  4009. }
  4010. };
  4011.  
  4012. // Returns a data URL of a gray chessboard image that indicates transparency
  4013. jsc.pub.chessboard = function (color) {
  4014. if (!color) {
  4015. color = "rgba(0,0,0,0)";
  4016. }
  4017. var preview = jsc.genColorPreviewCanvas(color);
  4018. return preview.canvas.toDataURL();
  4019. };
  4020.  
  4021. // Returns a data URL of a gray chessboard image that indicates transparency
  4022. jsc.pub.background = function (color) {
  4023. var backgrounds = [];
  4024.  
  4025. // CSS gradient for background color preview
  4026. backgrounds.push(jsc.genColorPreviewGradient(color));
  4027.  
  4028. // data URL of generated PNG image with a gray transparency chessboard
  4029. var preview = jsc.genColorPreviewCanvas();
  4030. backgrounds.push(
  4031. [
  4032. "url('" + preview.canvas.toDataURL() + "')",
  4033. "left top",
  4034. "repeat",
  4035. ].join(" ")
  4036. );
  4037.  
  4038. return backgrounds.join(", ");
  4039. };
  4040.  
  4041. //
  4042. // DEPRECATED properties and methods
  4043. //
  4044.  
  4045. // DEPRECATED. Use jscolor.presets.default instead.
  4046. //
  4047. // Custom default options for all color pickers, e.g. { hash: true, width: 300 }
  4048. jsc.pub.options = {};
  4049.  
  4050. // DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
  4051. //
  4052. // By default, we'll search for all elements with class="jscolor" and install a color picker on them.
  4053. //
  4054. // You can change what class name will be looked for by setting the property jscolor.lookupClass
  4055. // anywhere in your HTML document. To completely disable the automatic lookup, set it to null.
  4056. //
  4057. jsc.pub.lookupClass = "jscolor";
  4058.  
  4059. // DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
  4060. //
  4061. // Install jscolor on all elements that have the specified class name
  4062. jsc.pub.installByClassName = function () {
  4063. console.error(
  4064. 'jscolor.installByClassName() is DEPRECATED. Use data-jscolor="" attribute instead of a class name.' +
  4065. jsc.docsRef
  4066. );
  4067. return false;
  4068. };
  4069.  
  4070. jsc.register();
  4071.  
  4072. return jsc.pub;
  4073. })(); // END jscolor
  4074.  
  4075. if (typeof window.jscolor === "undefined") {
  4076. window.jscolor = window.JSColor = jscolor;
  4077. }
  4078.  
  4079. // END jscolor code
  4080.  
  4081. return jscolor;
  4082. }); // END factory