diff --git a/src/editor/components/PaintBox.js b/src/editor/components/PaintBox.js index 42c5385e..7840f102 100644 --- a/src/editor/components/PaintBox.js +++ b/src/editor/components/PaintBox.js @@ -1,4 +1,4 @@ -/* globals $ */ +import {jGraduate} from './jgraduate/jQuery.jGraduate.js'; /** * */ @@ -20,7 +20,7 @@ class PaintBox { let docElem = svgdocbox.documentElement; docElem = document.importNode(docElem, true); - container.append(docElem); + container.appendChild(docElem); this.rect = docElem.firstElementChild; this.defs = docElem.getElementsByTagName('defs')[0]; @@ -48,7 +48,7 @@ class PaintBox { case 'radialGradient': { this.grad.remove(); this.grad = paint[ptype]; - this.defs.append(this.grad); + this.defs.appendChild(this.grad); const id = this.grad.id = 'gradbox_' + this.type; fillAttr = 'url(#' + id + ')'; break; @@ -70,14 +70,14 @@ class PaintBox { const opts = {alpha: opac}; if (color.startsWith('url(#')) { let refElem = svgCanvas.getRefElem(color); - refElem = (refElem) ? refElem.cloneNode(true) : $('#' + type + '_color defs *')[0]; + refElem = (refElem) ? refElem.cloneNode(true) : document.querySelectorAll('#' + type + '_color defs *')[0]; opts[refElem.tagName] = refElem; } else if (color.startsWith('#')) { opts.solidColor = color.substr(1); } else { opts.solidColor = 'none'; } - return new $.jGraduate.Paint(opts); + return new jGraduate.Paint(opts); } /** diff --git a/src/editor/components/jgraduate/ColorValuePicker.js b/src/editor/components/jgraduate/ColorValuePicker.js new file mode 100644 index 00000000..fa39e9a6 --- /dev/null +++ b/src/editor/components/jgraduate/ColorValuePicker.js @@ -0,0 +1,392 @@ +/* eslint-disable max-len */ +/* eslint-disable unicorn/prefer-math-trunc */ +/* eslint-disable no-bitwise */ +/** +* @external Math +*/ +/** +* @memberof external:Math +* @param {Float} value +* @param {Float} precision +* @returns {Float} +*/ +function toFixedNumeric (value, precision) { + if (precision === undefined) precision = 0; + return Math.round(value * (10 ** precision)) / (10 ** precision); +} +/** + * Whether a value is `null` or `undefined`. + * @param {any} val + * @returns {boolean} + */ +const isNullish = (val) => { + return val === null || val === undefined; +}; +/** + * Controls for all the input elements for the typing in color values. + */ +export default class ColorValuePicker { + /** + * @param {external:jQuery} picker + * @param {external:jQuery.jPicker.Color} color + * @param {external:jQuery.fn.$.fn.jPicker} bindedHex + * @param {Float} alphaPrecision + */ + constructor (picker, color, bindedHex, alphaPrecision) { + const that = this; // private properties and methods + const inputs = picker.querySelectorAll('td.Text input'); + // input box key down - use arrows to alter color + /** + * + * @param {Event} e + * @returns {Event|false|void} + */ + function keyDown (e) { + if (e.target.value === '' && e.target !== hex && ((!isNullish(bindedHex) && e.target !== bindedHex) || isNullish(bindedHex))) return undefined; + if (!validateKey(e)) return e; + switch (e.target) { + case red: + switch (e.keyCode) { + case 38: + red.value = setValueInRange.call(that, (red.value << 0) + 1, 0, 255); + color.val('r', red.value, e.target); + return false; + case 40: + red.value = setValueInRange.call(that, (red.value << 0) - 1, 0, 255); + color.val('r', red.value, e.target); + return false; + } + break; + case green: + switch (e.keyCode) { + case 38: + green.value = setValueInRange.call(that, (green.value << 0) + 1, 0, 255); + color.val('g', green.value, e.target); + return false; + case 40: + green.value = setValueInRange.call(that, (green.value << 0) - 1, 0, 255); + color.val('g', green.value, e.target); + return false; + } + break; + case blue: + switch (e.keyCode) { + case 38: + blue.value = setValueInRange.call(that, (blue.value << 0) + 1, 0, 255); + color.val('b', blue.value, e.target); + return false; + case 40: + blue.value = setValueInRange.call(that, (blue.value << 0) - 1, 0, 255); + color.val('b', blue.value, e.target); + return false; + } + break; + case alpha: + switch (e.keyCode) { + case 38: + alpha.value = setValueInRange.call(that, Number.parseFloat(alpha.value) + 1, 0, 100); + color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target); + return false; + case 40: + alpha.value = setValueInRange.call(that, Number.parseFloat(alpha.value) - 1, 0, 100); + color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target); + return false; + } + break; + case hue: + switch (e.keyCode) { + case 38: + hue.value = setValueInRange.call(that, (hue.value << 0) + 1, 0, 360); + color.val('h', hue.value, e.target); + return false; + case 40: + hue.value =setValueInRange.call(that, (hue.value << 0) - 1, 0, 360); + color.val('h', hue.value, e.target); + return false; + } + break; + case saturation: + switch (e.keyCode) { + case 38: + saturation.value = setValueInRange.call(that, (saturation.value << 0) + 1, 0, 100); + color.val('s', saturation.value, e.target); + return false; + case 40: + saturation.value = setValueInRange.call(that, (saturation.value << 0) - 1, 0, 100); + color.val('s', saturation.value, e.target); + return false; + } + break; + case value: + switch (e.keyCode) { + case 38: + value.value = setValueInRange.call(that, (value.value << 0) + 1, 0, 100); + color.val('v', value.value, e.target); + return false; + case 40: + value.value = setValueInRange.call(that, (value.value << 0) - 1, 0, 100); + color.val('v', value.value, e.target); + return false; + } + break; + } + return undefined; + } + // input box key up - validate value and set color + /** + * @param {Event} e + * @returns {Event|void} + * @todo Why is this returning an event? + */ + function keyUp (e) { + if (e.target.value === '' && e.target !== hex && + ((!isNullish(bindedHex) && e.target !== bindedHex) || + isNullish(bindedHex))) return undefined; + if (!validateKey(e)) return e; + switch (e.target) { + case red: + red.value = setValueInRange.call(that, red.value, 0, 255); + color.val('r', red.value, e.target); + break; + case green: + green.value = setValueInRange.call(that, green.value, 0, 255); + color.val('g', green.value, e.target); + break; + case blue: + blue.value = setValueInRange.call(that, blue.value, 0, 255); + color.val('b', blue.value, e.target); + break; + case alpha: + alpha.value = setValueInRange.call(that, alpha.value, 0, 100); + color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target); + break; + case hue: + hue.value = setValueInRange.call(that, hue.value, 0, 360); + color.val('h', hue.value, e.target); + break; + case saturation: + saturation.value = setValueInRange.call(that, saturation.value, 0, 100); + color.val('s', saturation.value, e.target); + break; + case value: + value.value = setValueInRange.call(that, value.value, 0, 100); + color.val('v', value.value, e.target); + break; + case hex: + hex.value = hex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6); + bindedHex && bindedHex.val(hex.value); + color.val('hex', hex.value !== '' ? hex.value : null, e.target); + break; + case bindedHex: + bindedHex.value = bindedHex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6); + hex.val(bindedHex.value); + color.val('hex', bindedHex.value !== '' ? bindedHex.value : null, e.target); + break; + case ahex: + ahex.value = ahex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 2); + color.val('a', !isNullish(ahex.value) ? Number.parseInt(ahex.value, 16) : null, e.target); + break; + } + return undefined; + } + // input box blur - reset to original if value empty + /** + * @param {Event} e + * @returns {void} + */ + function blur (e) { + if (!isNullish(color.value)) { + switch (e.target) { + case red: + color.value = 'r'; + red.value = color.value; + break; + case green: + color.value = 'g'; + green.value = color.value; + break; + case blue: + color.value = 'b'; + blue.value = color.value; + break; + case alpha: + color.value = 'a'; + alpha.value = toFixedNumeric((color.value * 100) / 255, alphaPrecision); + break; + case hue: + color.value = 'h'; + hue.value = color.value; + break; + case saturation: + color.value = 's'; + saturation.value = color.value; + break; + case value: + color.value = 'v'; + value.value = color.value; + break; + case hex: + case bindedHex: + color.value = 'hex'; + hex.value = color.value; + bindedHex.value = color.value; + break; + case ahex: + color.value = 'ahex'; + ahex.value = color.value.substring(6); + break; + } + } + } + /** + * @param {Event} e + * @returns {boolean} + */ + function validateKey (e) { + switch (e.keyCode) { + case 9: + case 16: + case 29: + case 37: + case 39: + return false; + case 'c'.charCodeAt(): + case 'v'.charCodeAt(): + if (e.ctrlKey) return false; + } + return true; + } + + /** + * Constrain value within range. + * @param {Float|string} value + * @param {Float} min + * @param {Float} max + * @returns {Float|string} Returns a number or numeric string + */ + function setValueInRange (value, min, max) { + if (value === '' || isNaN(value)) return min; + if (value > max) return max; + if (value < min) return min; + return value; + } + /** + * @param {external:jQuery} ui + * @param {Element} context + * @returns {void} + */ + function colorChanged (ui, context) { + const all = ui.val('all'); + if (context !== red) red.value = (!isNullish(all) ? all.r : ''); + if (context !== green) green.value = (!isNullish(all) ? all.g : ''); + if (context !== blue) blue.value = (!isNullish(all) ? all.b : ''); + if (alpha && context !== alpha) alpha.value = (!isNullish(all) ? toFixedNumeric((all.a * 100) / 255, alphaPrecision) : ''); + if (context !== hue) hue.value = (!isNullish(all) ? all.h : ''); + if (context !== saturation) saturation.value = (!isNullish(all) ? all.s : ''); + if (context !== value) value.value = (!isNullish(all) ? all.v : ''); + if (context !== hex && ((bindedHex && context !== bindedHex) || !bindedHex)) hex.value = (!isNullish(all) ? all.hex : ''); + if (bindedHex && context !== bindedHex && context !== hex) bindedHex.value = (!isNullish(all) ? all.hex : ''); + if (ahex && context !== ahex) ahex.value = (!isNullish(all) ? all.ahex.substring(6) : ''); + } + /** + * Unbind all events and null objects. + * @returns {void} + */ + function destroy () { + red.removeEventListener('keyup', keyUp); + green.removeEventListener('keyup', keyUp); + blue.removeEventListener('keyup', keyUp); + hue.removeEventListener('keyup', keyUp); + saturation.removeEventListener('keyup', keyUp); + value.removeEventListener('keyup', keyUp); + hex.removeEventListener('keyup', keyUp); + + red.removeEventListener('blur', blur); + green.removeEventListener('blur', blur); + blue.removeEventListener('blur', blur); + hue.removeEventListener('blur', blur); + saturation.removeEventListener('blur', blur); + value.removeEventListener('blur', blur); + hex.removeEventListener('blur', blur); + + red.removeEventListener('keydown', keyDown); + green.removeEventListener('keydown', keyDown); + blue.removeEventListener('keydown', keyDown); + hue.removeEventListener('keydown', keyDown); + saturation.removeEventListener('keydown', keyDown); + value.removeEventListener('keydown', keyDown); + + if (alpha !== null) { + alpha.removeEventListener('keyup', keyUp); + alpha.removeEventListener('blur', blur); + alpha.removeEventListener('keydown', keyDown); + } + if (ahex !== null) { + ahex.removeEventListener('keyup', keyUp); + ahex.removeEventListener('blur', blur); + } + if (bindedHex !== null) { + bindedHex.removeEventListener('keyup', keyUp); + bindedHex.removeEventListener('blur', blur); + } + color.unbind(colorChanged); + red = null; + green = null; + blue = null; + alpha = null; + hue = null; + saturation = null; + value = null; + hex = null; + ahex = null; + } + let + red = inputs[3], + green = inputs[4], + blue = inputs[5], + alpha = inputs.length > 7 ? inputs[6] : null, + hue = inputs[0], + saturation = inputs[1], + value = inputs[2], + hex = inputs[(inputs.length > 7) ? 7 : 6], + ahex = inputs.length > 7 ? inputs[8] : null; + Object.assign(that, {destroy}); + red.addEventListener('keyup', keyUp); + green.addEventListener('keyup', keyUp); + blue.addEventListener('keyup', keyUp); + hue.addEventListener('keyup', keyUp); + saturation.addEventListener('keyup', keyUp); + value.addEventListener('keyup', keyUp); + hex.addEventListener('keyup', keyUp); + + red.addEventListener('blur', blur); + green.addEventListener('blur', blur); + blue.addEventListener('blur', blur); + hue.addEventListener('blur', blur); + saturation.addEventListener('blur', blur); + value.addEventListener('blur', blur); + hex.addEventListener('blur', blur); + + red.addEventListener('keydown', keyDown); + green.addEventListener('keydown', keyDown); + blue.addEventListener('keydown', keyDown); + hue.addEventListener('keydown', keyDown); + saturation.addEventListener('keydown', keyDown); + value.addEventListener('keydown', keyDown); + + if (alpha !== null) { + alpha.addEventListener('keyup', keyUp); + alpha.addEventListener('blur', blur); + alpha.addEventListener('keydown', keyDown); + } + if (ahex !== null) { + ahex.addEventListener('keyup', keyUp); + ahex.addEventListener('blur', blur); + } + if (bindedHex !== null) { + bindedHex.addEventListener('keyup', keyUp); + bindedHex.addEventListener('blur', blur); + } + color.bind(colorChanged); + } +} diff --git a/src/editor/components/jgraduate/Slider.js b/src/editor/components/jgraduate/Slider.js new file mode 100644 index 00000000..8f736a48 --- /dev/null +++ b/src/editor/components/jgraduate/Slider.js @@ -0,0 +1,340 @@ +/* eslint-disable unicorn/prefer-math-trunc */ +/* eslint-disable no-bitwise */ +/* eslint-disable unicorn/prefer-ternary */ +import {findPos} from './Util.js'; +/** + * Whether a value is `null` or `undefined`. + * @param {any} val + * @returns {boolean} + */ +const isNullish = (val) => { + return val === null || val === undefined; +}; +/** + * Encapsulate slider functionality for the ColorMap and ColorBar - + * could be useful to use a jQuery UI draggable for this with certain extensions. + * @memberof module:jPicker + */ +export default class Slider { + /** + * @param {external:jQuery} bar + * @param {module:jPicker.SliderOptions} options + */ + constructor (bar, options) { + const that = this; + /** + * Fire events on the supplied `context` + * @param {module:jPicker.JPickerInit} context + * @returns {void} + */ + function fireChangeEvents (context) { + changeEvents.forEach((changeEvent) => { + changeEvent.call(that, that, context); + }); + } + + /** + * Bind the mousedown to the bar not the arrow for quick snapping to the clicked location. + * @param {external:jQuery.Event} e + * @returns {void} + */ + function mouseDown (e) { + const off = findPos(bar); + offset = {l: off.left | 0, t: off.top | 0}; + clearTimeout(timeout); + // using setTimeout for visual updates - once the style is updated the browser will re-render internally allowing the next Javascript to run + timeout = setTimeout(function () { + setValuesFromMousePosition.call(that, e); + }, 0); + // Bind mousemove and mouseup event to the document so it responds when dragged of of the bar - we will unbind these when on mouseup to save processing + document.addEventListener('mousemove', mouseMove); + document.addEventListener('mouseup', mouseUp); + e.preventDefault(); // don't try to select anything or drag the image to the desktop + } + /** + * Set the values as the mouse moves. + * @param {external:jQuery.Event} e + * @returns {false} + */ + function mouseMove (e) { + clearTimeout(timeout); + timeout = setTimeout(function () { + setValuesFromMousePosition.call(that, e); + }, 0); + e.stopPropagation(); + e.preventDefault(); + return false; + } + /** + * Unbind the document events - they aren't needed when not dragging. + * @param {external:jQuery.Event} e + * @returns {false} + */ + function mouseUp (e) { + document.removeEventListener('mousemove', mouseMove); + document.removeEventListener('mouseup', mouseUp); + e.stopPropagation(); + e.preventDefault(); + return false; + } + + /** + * Calculate mouse position and set value within the current range. + * @param {Event} e + * @returns {void} + */ + function setValuesFromMousePosition (e) { + const barW = bar.w, // local copies for YUI compressor + barH = bar.h; + let locX = e.pageX - offset.l, + locY = e.pageY - offset.t; + // keep the arrow within the bounds of the bar + if (locX < 0) locX = 0; + else if (locX > barW) locX = barW; + if (locY < 0) locY = 0; + else if (locY > barH) locY = barH; + val.call(that, 'xy', { + x: ((locX / barW) * rangeX) + minX, + y: ((locY / barH) * rangeY) + minY + }); + } + /** + * + * @returns {void} + */ + function draw () { + const + barW = bar.w, + barH = bar.h, + arrowW = arrow.w, + arrowH = arrow.h; + let arrowOffsetX = 0, + arrowOffsetY = 0; + setTimeout(function () { + if (rangeX > 0) { // range is greater than zero + // constrain to bounds + if (x === maxX) arrowOffsetX = barW; + else arrowOffsetX = ((x / rangeX) * barW) | 0; + } + if (rangeY > 0) { // range is greater than zero + // constrain to bounds + if (y === maxY) arrowOffsetY = barH; + else arrowOffsetY = ((y / rangeY) * barH) | 0; + } + // if arrow width is greater than bar width, center arrow and prevent horizontal dragging + if (arrowW >= barW) arrowOffsetX = (barW >> 1) - (arrowW >> 1); // number >> 1 - superfast bitwise divide by two and truncate (move bits over one bit discarding lowest) + else arrowOffsetX -= arrowW >> 1; + // if arrow height is greater than bar height, center arrow and prevent vertical dragging + if (arrowH >= barH) arrowOffsetY = (barH >> 1) - (arrowH >> 1); + else arrowOffsetY -= arrowH >> 1; + // set the arrow position based on these offsets + arrow.style.left = arrowOffsetX + 'px'; + arrow.style.top = arrowOffsetY + 'px'; + }); + } + + /** + * Get or set a value. + * @param {?("xy"|"x"|"y")} name + * @param {module:math.XYObject} value + * @param {module:jPicker.Slider} context + * @returns {module:math.XYObject|Float|void} + */ + function val (name, value, context) { + const set = value !== undefined; + if (!set) { + if (isNullish(name)) name = 'xy'; + switch (name.toLowerCase()) { + case 'x': return x; + case 'y': return y; + case 'xy': + default: return {x, y}; + } + } + if (!isNullish(context) && context === that) return undefined; + let changed = false; + + let newX, newY; + if (isNullish(name)) name = 'xy'; + switch (name.toLowerCase()) { + case 'x': + newX = (value && ((value.x && value.x | 0) || value | 0)) || 0; + break; + case 'y': + newY = (value && ((value.y && value.y | 0) || value | 0)) || 0; + break; + case 'xy': + default: + newX = (value && value.x && value.x | 0) || 0; + newY = (value && value.y && value.y | 0) || 0; + break; + } + if (!isNullish(newX)) { + if (newX < minX) newX = minX; + else if (newX > maxX) newX = maxX; + if (x !== newX) { + x = newX; + changed = true; + } + } + if (!isNullish(newY)) { + if (newY < minY) newY = minY; + else if (newY > maxY) newY = maxY; + if (y !== newY) { + y = newY; + changed = true; + } + } + changed && fireChangeEvents.call(that, context || that); + return undefined; + } + + /** + * @typedef {PlainObject} module:jPicker.MinMaxRangeX + * @property {Float} minX + * @property {Float} maxX + * @property {Float} rangeX + */ + /** + * @typedef {PlainObject} module:jPicker.MinMaxRangeY + * @property {Float} minY + * @property {Float} maxY + * @property {Float} rangeY + */ + /** + * @typedef {module:jPicker.MinMaxRangeY|module:jPicker.MinMaxRangeX} module:jPicker.MinMaxRangeXY + */ + + /** + * + * @param {"minx"|"maxx"|"rangex"|"miny"|"maxy"|"rangey"|"all"} name + * @param {module:jPicker.MinMaxRangeXY} value + * @returns {module:jPicker.MinMaxRangeXY|module:jPicker.MinMaxRangeX|module:jPicker.MinMaxRangeY|void} + */ + function range (name, value) { + const set = value !== undefined; + if (!set) { + if (isNullish(name)) name = 'all'; + switch (name.toLowerCase()) { + case 'minx': return minX; + case 'maxx': return maxX; + case 'rangex': return {minX, maxX, rangeX}; + case 'miny': return minY; + case 'maxy': return maxY; + case 'rangey': return {minY, maxY, rangeY}; + case 'all': + default: return {minX, maxX, rangeX, minY, maxY, rangeY}; + } + } + let // changed = false, + newMinX, + newMaxX, + newMinY, + newMaxY; + if (isNullish(name)) name = 'all'; + switch (name.toLowerCase()) { + case 'minx': + newMinX = (value && ((value.minX && value.minX | 0) || value | 0)) || 0; + break; + case 'maxx': + newMaxX = (value && ((value.maxX && value.maxX | 0) || value | 0)) || 0; + break; + case 'rangex': + newMinX = (value && value.minX && value.minX | 0) || 0; + newMaxX = (value && value.maxX && value.maxX | 0) || 0; + break; + case 'miny': + newMinY = (value && ((value.minY && value.minY | 0) || value | 0)) || 0; + break; + case 'maxy': + newMaxY = (value && ((value.maxY && value.maxY | 0) || value | 0)) || 0; + break; + case 'rangey': + newMinY = (value && value.minY && value.minY | 0) || 0; + newMaxY = (value && value.maxY && value.maxY | 0) || 0; + break; + case 'all': + default: + newMinX = (value && value.minX && value.minX | 0) || 0; + newMaxX = (value && value.maxX && value.maxX | 0) || 0; + newMinY = (value && value.minY && value.minY | 0) || 0; + newMaxY = (value && value.maxY && value.maxY | 0) || 0; + break; + } + + if (!isNullish(newMinX) && minX !== newMinX) { + minX = newMinX; + rangeX = maxX - minX; + } + if (!isNullish(newMaxX) && maxX !== newMaxX) { + maxX = newMaxX; + rangeX = maxX - minX; + } + if (!isNullish(newMinY) && minY !== newMinY) { + minY = newMinY; + rangeY = maxY - minY; + } + if (!isNullish(newMaxY) && maxY !== newMaxY) { + maxY = newMaxY; + rangeY = maxY - minY; + } + return undefined; + } + /** + * @param {GenericCallback} callback + * @returns {void} + */ + function bind (callback) { // eslint-disable-line promise/prefer-await-to-callbacks + if (typeof callback === 'function') changeEvents.push(callback); + } + /** + * @param {GenericCallback} callback + * @returns {void} + */ + function unbind (callback) { // eslint-disable-line promise/prefer-await-to-callbacks + if (typeof callback !== 'function') return; + let i; + while ((i = changeEvents.includes(callback))) changeEvents.splice(i, 1); + } + /** + * + * @returns {void} + */ + function destroy () { + // unbind all possible events and null objects + document.removeEventListener('mousemove', mouseMove); + document.removeEventListener('mouseup', mouseUp); + bar.removeEventListener('mousedown', mouseDown); + bar = null; + arrow = null; + changeEvents = null; + } + let offset, + timeout, + x = 0, + y = 0, + minX = 0, + maxX = 100, + rangeX = 100, + minY = 0, + maxY = 100, + rangeY = 100, + arrow = bar.querySelector('img'), // the arrow image to drag + changeEvents = []; + Object.assign(that, { + val, + range, + bind, + unbind, + destroy + }); + // initialize this control + arrow.src = options.arrow && options.arrow.image; + arrow.w = (options.arrow && options.arrow.width) || arrow.width(); + arrow.h = (options.arrow && options.arrow.height) || arrow.height(); + bar.w = (options.map && options.map.width) || bar.width(); + bar.h = (options.map && options.map.height) || bar.height(); + bar.addEventListener('mousedown', mouseDown); + bind.call(that, draw); + } +} diff --git a/src/editor/components/jgraduate/Util.js b/src/editor/components/jgraduate/Util.js new file mode 100644 index 00000000..a0cc92b7 --- /dev/null +++ b/src/editor/components/jgraduate/Util.js @@ -0,0 +1,37 @@ +/** + * @param {any} obj + * @returns {any} + */ +export function findPos (obj) { + let curleft = 0; + let curtop = 0; + if (obj.offsetParent) { + do { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } while (obj = obj.offsetParent); + return {left: curleft, top: curtop}; + } + return {left: curleft, top: curtop}; +} + +export function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item)); +} + +export function mergeDeep(target, source) { + let output = Object.assign({}, target); + if (isObject(target) && isObject(source)) { + Object.keys(source).forEach(key => { + if (isObject(source[key])) { + if (!(key in target)) + Object.assign(output, { [key]: source[key] }); + else + output[key] = mergeDeep(target[key], source[key]); + } else { + Object.assign(output, { [key]: source[key] }); + } + }); + } + return output; +} \ No newline at end of file diff --git a/src/editor/components/jgraduate/jQuery.jGraduate.js b/src/editor/components/jgraduate/jQuery.jGraduate.js index 9fa0e3f3..e9df23b1 100644 --- a/src/editor/components/jgraduate/jQuery.jGraduate.js +++ b/src/editor/components/jgraduate/jQuery.jGraduate.js @@ -1,3 +1,7 @@ +/* eslint-disable no-loop-func */ +/* eslint-disable unicorn/prefer-node-remove */ +/* eslint-disable prefer-destructuring */ +/* eslint-disable no-unsanitized/property */ /** * @file jGraduate 0.4 * @@ -16,6 +20,9 @@ * @example $.jGraduate.Paint({radialGradient: o, a: 7}); // creates a radial gradient paint with opacity=0.07 * @example $.jGraduate.Paint({hex: '#rrggbb', linearGradient: o}); // throws an exception? */ +import Paint from './paint.js'; +import {jPickerDefaults, jPickerMethod} from './jQuery.jPicker.js'; +import {findPos} from './Util.js'; /** * @todo JFH: This jQuery plugin was adapted to work within a Web Component. @@ -53,1269 +60,1261 @@ if (!window.console) { * @param {external:jQuery} $ The jQuery instance to wrap * @returns {external:jQuery} */ -export default function jQueryPluginJGraduate ($) { +// export default function jQueryPluginJGraduate ($) { +/* eslint-disable jsdoc/require-property */ +/** +* @namespace {PlainObject} jGraduate +* @memberof external:jQuery +*/ +export const jGraduate = /** @lends external:jQuery.jGraduate */ { + /* eslint-enable jsdoc/require-property */ /** - * @typedef {PlainObject} module:jGraduate.jGraduatePaintOptions - * @property {Float} [alpha] - * @property {module:jGraduate~Paint} [copy] Copy paint object - * @property {SVGLinearGradientElement} [linearGradient] - * @property {SVGRadialGradientElement} [radialGradient] - * @property {string} [solidColor] + * @class external:jQuery.jGraduate.Paint + * @see module:jGraduate~Paint */ + Paint +}; +// JSDoc doesn't show this as belonging to our `module:jGraduate.Options` type, +// so we use `@see` +/** +* @namespace {module:jGraduate.Options} jGraduateDefaults +* @memberof external:jQuery.fn +*/ +export const jGraduateDefaults = /** @lends external:jQuery.fn.jGraduateDefaults */ { /** - * @memberof module:jGraduate~ + * Creates an object with a 'none' color. + * @type {external:jQuery.jGraduate.Paint} + * @see module:jGraduate.Options */ - class Paint { - /** - * @param {module:jGraduate.jGraduatePaintOptions} [opt] - */ - constructor (opt) { - const options = opt || {}; - this.alpha = isNaN(options.alpha) ? 100 : options.alpha; - // copy paint object - if (options.copy) { - /** - * @name module:jGraduate~Paint#type - * @type {"none"|"solidColor"|"linearGradient"|"radialGradient"} - */ - this.type = options.copy.type; - /** - * Represents opacity (0-100). - * @name module:jGraduate~Paint#alpha - * @type {Float} - */ - this.alpha = options.copy.alpha; - /** - * Represents #RRGGBB hex of color. - * @name module:jGraduate~Paint#solidColor - * @type {string} - */ - this.solidColor = null; - /** - * @name module:jGraduate~Paint#linearGradient - * @type {SVGLinearGradientElement} - */ - this.linearGradient = null; - /** - * @name module:jGraduate~Paint#radialGradient - * @type {SVGRadialGradientElement} - */ - this.radialGradient = null; - - switch (this.type) { - case 'none': - break; - case 'solidColor': - this.solidColor = options.copy.solidColor; - break; - case 'linearGradient': - this.linearGradient = options.copy.linearGradient.cloneNode(true); - break; - case 'radialGradient': - this.radialGradient = options.copy.radialGradient.cloneNode(true); - break; - } - // create linear gradient paint - } else if (options.linearGradient) { - this.type = 'linearGradient'; - this.solidColor = null; - this.radialGradient = null; - this.linearGradient = options.linearGradient.cloneNode(true); - // create linear gradient paint - } else if (options.radialGradient) { - this.type = 'radialGradient'; - this.solidColor = null; - this.linearGradient = null; - this.radialGradient = options.radialGradient.cloneNode(true); - // create solid color paint - } else if (options.solidColor) { - this.type = 'solidColor'; - this.solidColor = options.solidColor; - // create empty paint - } else { - this.type = 'none'; - this.solidColor = null; - this.linearGradient = null; - this.radialGradient = null; - } - } - } - + paint: new jGraduate.Paint(), /** - * @namespace {PlainObject} jGraduate - * @memberof external:jQuery + * @namespace */ - $.jGraduate = /** @lends external:jQuery.jGraduate */ { - /** - * @class external:jQuery.jGraduate.Paint - * @see module:jGraduate~Paint - */ - Paint - }; - - // JSDoc doesn't show this as belonging to our `module:jGraduate.Options` type, - // so we use `@see` - /** - * @namespace {module:jGraduate.Options} jGraduateDefaults - * @memberof external:jQuery.fn - */ - $.fn.jGraduateDefaults = /** @lends external:jQuery.fn.jGraduateDefaults */ { - /** - * Creates an object with a 'none' color. - * @type {external:jQuery.jGraduate.Paint} - * @see module:jGraduate.Options - */ - paint: new $.jGraduate.Paint(), - /** - * @namespace - */ - window: { - /** - * @type {string} - * @see module:jGraduate.Options - */ - pickerTitle: 'Drag markers to pick a paint' - }, - /** - * @namespace - */ - images: { - /** - * @type {string} - * @see module:jGraduate.Options - */ - clientPath: 'images/' - }, + window: { /** * @type {string} * @see module:jGraduate.Options */ - newstop: 'inverse' // same, inverse, black, white - }; - - const isGecko = navigator.userAgent.includes('Gecko/'); - + pickerTitle: 'Drag markers to pick a paint' + }, /** - * @typedef {PlainObject} module:jGraduate.Attrs + * @namespace */ + images: { + /** + * @type {string} + * @see module:jGraduate.Options + */ + clientPath: 'images/' + }, /** - * @param {SVGElement} elem - * @param {module:jGraduate.Attrs} attrs - * @returns {void} + * @type {string} + * @see module:jGraduate.Options */ - function setAttrs (elem, attrs) { - if (isGecko) { - Object.entries(attrs).forEach(([aname, val]) => { - elem.setAttribute(aname, val); - }); - } else { - Object.entries(attrs).forEach(([aname, val]) => { - const prop = elem[aname]; - if (prop && prop.constructor === 'SVGLength') { - prop.baseVal.value = val; - } else { - elem.setAttribute(aname, val); - } - }); - } - } - - /** - * @param {string} name - * @param {module:jGraduate.Attrs} attrs - * @param {Element} newparent - * @returns {SVGElement} - */ - function mkElem (name, attrs, newparent) { - const elem = document.createElementNS(ns.svg, name); - setAttrs(elem, attrs); - if (newparent) { - newparent.append(elem); - } - return elem; - } - - /** - * @typedef {PlainObject} module:jGraduate.ColorOpac Object may have one or both values - * @property {string} [color] #Hex color - * @property {Float} [opac] 0-1 - */ - /** - * @typedef {PlainObject} module:jGraduate.Options - * @property {module:jGraduate~Paint} [paint] A Paint object object describing the paint to display initially; defaults to a new instance without options (defaults to opaque white) - * @property {external:Window} [window] - * @property {string} [window.pickerTitle="Drag markers to pick a paint"] - * @property {PlainObject} [images] - * @property {string} [images.clientPath="images/"] - * @property {"same"|"inverse"|"black"|"white"|module:jGraduate.ColorOpac} [newstop="inverse"] - */ - - /** - * @callback external:jQuery.fn.jGraduate.OkCallback - * @param {external:jQuery.jGraduate.Paint} paint - * @returns {void} - */ - /** - * @callback external:jQuery.fn.jGraduate.CancelCallback - * @returns {void} - */ - - /** - * @function external:jQuery.fn.jGraduate - * @param {module:jGraduate.Options} [options] - * @param {external:jQuery.fn.jGraduate.OkCallback} [okCallback] Called with a Paint object when Ok is pressed - * @param {external:jQuery.fn.jGraduate.CancelCallback} [cancelCallback] Called with no arguments when Cancel is pressed - * @returns {external:jQuery} - */ - $.fn.jGraduate = function (options, okCallback, cancelCallback) { - return this.each(function () { - const $this = $(this), - $settings = $.extend(true, {}, $.fn.jGraduateDefaults, options || {}), - id = $this.attr('id'), - idref = '#' + $this.attr('id') + ' '; - // JFH !!!!! - const $shadowRoot = this.parentNode; - const $wc = (selector) => $($shadowRoot.querySelectorAll(selector)); - - if (!idref) { - // eslint-disable-next-line no-alert - alert('Container element must have an id attribute to maintain unique id strings for sub-elements.'); - return; - } - - const okClicked = function () { - switch ($this.paint.type) { - case 'radialGradient': - $this.paint.linearGradient = null; - break; - case 'linearGradient': - $this.paint.radialGradient = null; - break; - case 'solidColor': - $this.paint.radialGradient = $this.paint.linearGradient = null; - break; - } - typeof $this.okCallback === 'function' && $this.okCallback($this.paint); - $this.hide(); - }; - const cancelClicked = function () { - typeof $this.cancelCallback === 'function' && $this.cancelCallback(); - $this.hide(); - }; - - $.extend( - true, - $this, - // public properties, methods, and callbacks - { - // make a copy of the incoming paint - paint: new $.jGraduate.Paint({copy: $settings.paint}), - okCallback: typeof okCallback === 'function' ? okCallback : null, - cancelCallback: typeof cancelCallback === 'function' ? cancelCallback : null - } - ); - - let // pos = $this.position(), - color = null; - const $win = $(window); - - if ($this.paint.type === 'none') { - $this.paint = new $.jGraduate.Paint({solidColor: 'ffffff'}); - } - - $this.addClass('jGraduate_Picker'); - /* eslint-disable max-len */ - $this.html( - '' + - '
' + - '
' + - '
' + - '
' - ); - /* JFH !!!! */ - const colPicker = $wc(idref + '> .jGraduate_colPick'); - const gradPicker = $wc(idref + '> .jGraduate_gradPick'); - - gradPicker.html( - '
' + - '

' + $settings.window.pickerTitle + '

' + - '
' + - '
' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '' + - '
' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '
' + - '' + - '' + - '
' - ); - /* eslint-enable max-len */ - // -------------- - // Set up all the SVG elements (the gradient, stops and rectangle) - const MAX = 256, - MARGINX = 0, - MARGINY = 0, - // STOP_RADIUS = 15 / 2, - SIZEX = MAX - 2 * MARGINX, - SIZEY = MAX - 2 * MARGINY; - - const attrInput = {}; - - const SLIDERW = 145; - $wc('.jGraduate_SliderBar').width(SLIDERW); - // JFH !!!!!! - const container = $wc('#' + id + '_jGraduate_GradContainer')[0]; - - const svg = mkElem('svg', { - id: id + '_jgraduate_svg', - width: MAX, - height: MAX, - xmlns: ns.svg - }, container); - - // This wasn't working as designed - // let curType; - // curType = curType || $this.paint.type; - - // if we are sent a gradient, import it - let curType = $this.paint.type; - - let grad = $this.paint[curType]; - let curGradient = grad; - - const gradalpha = $this.paint.alpha; - - const isSolid = curType === 'solidColor'; - - // Make any missing gradients - switch (curType) { - case 'solidColor': - // fall through - case 'linearGradient': - if (!isSolid) { - curGradient.id = id + '_lg_jgraduate_grad'; - grad = curGradient = svg.appendChild(curGradient); - } - mkElem('radialGradient', { - id: id + '_rg_jgraduate_grad' - }, svg); - if (curType === 'linearGradient') { break; } - // fall through - case 'radialGradient': - if (!isSolid) { - curGradient.id = id + '_rg_jgraduate_grad'; - grad = curGradient = svg.appendChild(curGradient); - } - mkElem('linearGradient', { - id: id + '_lg_jgraduate_grad' - }, svg); - } - - let stopGroup; // eslint-disable-line prefer-const - if (isSolid) { - // JFH !!!!!!!! - grad = curGradient = $wc('#' + id + '_lg_jgraduate_grad')[0]; - color = $this.paint[curType]; - mkStop(0, '#' + color, 1); - - const type = typeof $settings.newstop; - - if (type === 'string') { - switch ($settings.newstop) { - case 'same': - mkStop(1, '#' + color, 1); - break; - - case 'inverse': { - // Invert current color for second stop - let inverted = ''; - for (let i = 0; i < 6; i += 2) { - // const ch = color.substr(i, 2); - let inv = (255 - Number.parseInt(color.substr(i, 2), 16)).toString(16); - if (inv.length < 2) inv = 0 + inv; - inverted += inv; - } - mkStop(1, '#' + inverted, 1); - break; - } case 'white': - mkStop(1, '#ffffff', 1); - break; - - case 'black': - mkStop(1, '#000000', 1); - break; - } - } else if (type === 'object') { - const opac = ('opac' in $settings.newstop) ? $settings.newstop.opac : 1; - mkStop(1, ($settings.newstop.color || '#' + color), opac); - } - } - - const x1 = Number.parseFloat(grad.getAttribute('x1') || 0.0), - y1 = Number.parseFloat(grad.getAttribute('y1') || 0.0), - x2 = Number.parseFloat(grad.getAttribute('x2') || 1.0), - y2 = Number.parseFloat(grad.getAttribute('y2') || 0.0); - - const cx = Number.parseFloat(grad.getAttribute('cx') || 0.5), - cy = Number.parseFloat(grad.getAttribute('cy') || 0.5), - fx = Number.parseFloat(grad.getAttribute('fx') || cx), - fy = Number.parseFloat(grad.getAttribute('fy') || cy); - - const previewRect = mkElem('rect', { - id: id + '_jgraduate_rect', - x: MARGINX, - y: MARGINY, - width: SIZEX, - height: SIZEY, - fill: 'url(#' + id + '_jgraduate_grad)', - 'fill-opacity': gradalpha / 100 - }, svg); - - // stop visuals created here - const beginCoord = $('
').attr({ - class: 'grad_coord jGraduate_lg_field', - title: 'Begin Stop' - }).text(1).css({ - top: y1 * MAX, - left: x1 * MAX - }).data('coord', 'start').appendTo(container); - - const endCoord = beginCoord.clone().text(2).css({ - top: y2 * MAX, - left: x2 * MAX - }).attr('title', 'End stop').data('coord', 'end').appendTo(container); - - const centerCoord = $('
').attr({ - class: 'grad_coord jGraduate_rg_field', - title: 'Center stop' - }).text('C').css({ - top: cy * MAX, - left: cx * MAX - }).data('coord', 'center').appendTo(container); - - const focusCoord = centerCoord.clone().text('F').css({ - top: fy * MAX, - left: fx * MAX, - display: 'none' - }).attr('title', 'Focus point').data('coord', 'focus').appendTo(container); - - focusCoord[0].id = id + '_jGraduate_focusCoord'; - - let showFocus; - $.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function (i, attr) { - const isRadial = isNaN(attr[1]); - - let attrval = curGradient.getAttribute(attr); - if (!attrval) { - // Set defaults - if (isRadial) { - // For radial points - attrval = '0.5'; - } else { - // Only x2 is 1 - attrval = attr === 'x2' ? '1.0' : '0.0'; - } - } - - attrInput[attr] = $wc('#' + id + '_jGraduate_' + attr) - .val(attrval) - .change(function () { - // TODO: Support values < 0 and > 1 (zoomable preview?) - if (isNaN(Number.parseFloat(this.value)) || this.value < 0) { - this.value = 0.0; - } else if (this.value > 1) { - this.value = 1.0; - } - - if (!(attr[0] === 'f' && - !showFocus) && - ((isRadial && curType === 'radialGradient') || (!isRadial && curType === 'linearGradient'))) { - curGradient.setAttribute(attr, this.value); - } - - const $elem = isRadial - ? attr[0] === 'c' ? centerCoord : focusCoord - : attr[1] === '1' ? beginCoord : endCoord; - - const cssName = attr.includes('x') ? 'left' : 'top'; - - $elem.css(cssName, this.value * MAX); - }).change(); - }); - - /** - * - * @param {Float} n - * @param {Float|string} colr - * @param {Float} opac - * @param {boolean} [sel] - * @param {SVGStopElement} [stopElem] - * @returns {SVGStopElement} - */ - function mkStop (n, colr, opac, sel, stopElem) { - const stop = stopElem || mkElem('stop', { - 'stop-color': colr, - 'stop-opacity': opac, - offset: n - }, curGradient); - if (stopElem) { - colr = stopElem.getAttribute('stop-color'); - opac = stopElem.getAttribute('stop-opacity'); - n = stopElem.getAttribute('offset'); - } else { - curGradient.append(stop); - } - if (opac === null) opac = 1; - - const pickerD = 'M-6.2,0.9c3.6-4,6.7-4.3,6.7-12.4c-0.2,7.9,' + - '3.1,8.8,6.5,12.4c3.5,3.8,2.9,9.6,0,12.3c-3.1,2.8-10.4,' + - '2.7-13.2,0C-9.6,9.9-9.4,4.4-6.2,0.9z'; - - const pathbg = mkElem('path', { - d: pickerD, - fill: 'url(#jGraduate_trans)', - transform: 'translate(' + (10 + n * MAX) + ', 26)' - }, stopGroup); - - const path = mkElem('path', { - d: pickerD, - fill: colr, - 'fill-opacity': opac, - transform: 'translate(' + (10 + n * MAX) + ', 26)', - stroke: '#000', - 'stroke-width': 1.5 - }, stopGroup); - - $(path).mousedown(function (e) { - selectStop(this); - drag = curStop; - $win.mousemove(dragColor).mouseup(remDrags); - stopOffset = stopMakerDiv.offset(); - e.preventDefault(); - return false; - }).data('stop', stop).data('bg', pathbg).dblclick(function () { - $wc('div.jGraduate_LightBox').show(); - const colorhandle = this; - let stopOpacity = Number(stop.getAttribute('stop-opacity')) || 1; - let stopColor = stop.getAttribute('stop-color') || 1; - let thisAlpha = (Number.parseFloat(stopOpacity) * 255).toString(16); - while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } - colr = stopColor.substr(1) + thisAlpha; - $wc('#' + id + '_jGraduate_stopPicker').css({left: 100, bottom: 15}).jPicker({ - window: {title: 'Pick the start color and opacity for the gradient'}, - images: {clientPath: $settings.images.clientPath}, - color: {active: colr, alphaSupport: true} - }, function (clr, arg2) { - stopColor = clr.val('hex') ? ('#' + clr.val('hex')) : 'none'; - stopOpacity = clr.val('a') !== null ? clr.val('a') / 256 : 1; - colorhandle.setAttribute('fill', stopColor); - colorhandle.setAttribute('fill-opacity', stopOpacity); - stop.setAttribute('stop-color', stopColor); - stop.setAttribute('stop-opacity', stopOpacity); - $wc('div.jGraduate_LightBox').hide(); - $wc('#' + id + '_jGraduate_stopPicker').hide(); - }, null, function () { - $wc('div.jGraduate_LightBox').hide(); - $wc('#' + id + '_jGraduate_stopPicker').hide(); - }); - }); - - $(curGradient).find('stop').each(function () { - const curS = $(this); - if (Number(this.getAttribute('offset')) > n) { - if (!colr) { - const newcolor = this.getAttribute('stop-color'); - const newopac = this.getAttribute('stop-opacity'); - stop.setAttribute('stop-color', newcolor); - path.setAttribute('fill', newcolor); - stop.setAttribute('stop-opacity', newopac === null ? 1 : newopac); - path.setAttribute('fill-opacity', newopac === null ? 1 : newopac); - } - curS.before(stop); - return false; - } - return true; - }); - if (sel) selectStop(path); - return stop; - } - - /** - * - * @returns {void} - */ - function remStop () { - delStop.setAttribute('display', 'none'); - const path = $wc(curStop); - const stop = path.data('stop'); - const bg = path.data('bg'); - $([curStop, stop, bg]).remove(); - } - - const stopMakerDiv = $wc('#' + id + '_jGraduate_StopSlider'); - - let stops, curStop, drag; - - const delStop = mkElem('path', { - d: 'm9.75,-6l-19.5,19.5m0,-19.5l19.5,19.5', - fill: 'none', - stroke: '#D00', - 'stroke-width': 5, - display: 'none' - }, undefined); // stopMakerSVG); - - /** - * @param {Element} item - * @returns {void} - */ - function selectStop (item) { - if (curStop) curStop.setAttribute('stroke', '#000'); - item.setAttribute('stroke', 'blue'); - curStop = item; - } - - let stopOffset; - - /** - * - * @returns {void} - */ - function remDrags () { - $win.unbind('mousemove', dragColor); - if (delStop.getAttribute('display') !== 'none') { - remStop(); - } - drag = null; - } - - let scaleX = 1, scaleY = 1, angle = 0; - - let cX = cx; - let cY = cy; - /** - * - * @returns {void} - */ - function xform () { - const rot = angle ? 'rotate(' + angle + ',' + cX + ',' + cY + ') ' : ''; - if (scaleX === 1 && scaleY === 1) { - curGradient.removeAttribute('gradientTransform'); - // $wc('#ang').addClass('dis'); - } else { - const x = -cX * (scaleX - 1); - const y = -cY * (scaleY - 1); - curGradient.setAttribute( - 'gradientTransform', - rot + 'translate(' + x + ',' + y + ') scale(' + - scaleX + ',' + scaleY + ')' - ); - // $wc('#ang').removeClass('dis'); - } - } - - /** - * @param {Event} evt - * @returns {void} - */ - function dragColor (evt) { - let x = evt.pageX - stopOffset.left; - const y = evt.pageY - stopOffset.top; - x = x < 10 - ? 10 - : x > MAX + 10 - ? MAX + 10 - : x; - - const xfStr = 'translate(' + x + ', 26)'; - if (y < -60 || y > 130) { - delStop.setAttribute('display', 'block'); - delStop.setAttribute('transform', xfStr); - } else { - delStop.setAttribute('display', 'none'); - } - - drag.setAttribute('transform', xfStr); - $.data(drag, 'bg').setAttribute('transform', xfStr); - const stop = $.data(drag, 'stop'); - const sX = (x - 10) / MAX; - - stop.setAttribute('offset', sX); - - let last = 0; - $(curGradient).find('stop').each(function (i) { - const cur = this.getAttribute('offset'); - const t = $(this); - if (cur < last) { - t.prev().before(t); - stops = $(curGradient).find('stop'); - } - last = cur; - }); - } - - const stopMakerSVG = mkElem('svg', { - width: '100%', - height: 45 - }, stopMakerDiv[0]); - - const transPattern = mkElem('pattern', { - width: 16, - height: 16, - patternUnits: 'userSpaceOnUse', - id: 'jGraduate_trans' - }, stopMakerSVG); - - const transImg = mkElem('image', { - width: 16, - height: 16 - }, transPattern); - - const bgImage = $settings.images.clientPath + 'map-opacity.png'; - - transImg.setAttributeNS(ns.xlink, 'xlink:href', bgImage); - - $(stopMakerSVG).click(function (evt) { - stopOffset = stopMakerDiv.offset(); - const {target} = evt; - if (target.tagName === 'path') return; - let x = evt.pageX - stopOffset.left - 8; - x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x; - mkStop(x / MAX, 0, 0, true); - evt.stopPropagation(); - }); - - $(stopMakerSVG).mouseover(function () { - stopMakerSVG.append(delStop); - }); - - stopGroup = mkElem('g', {}, stopMakerSVG); - - mkElem('line', { - x1: 10, - y1: 15, - x2: MAX + 10, - y2: 15, - 'stroke-width': 2, - stroke: '#000' - }, stopMakerSVG); - - const spreadMethodOpt = gradPicker.find('.jGraduate_spreadMethod').change(function () { - curGradient.setAttribute('spreadMethod', $(this).val()); - }); - - // handle dragging the stop around the swatch - let draggingCoord = null; - - const onCoordDrag = function (evt) { - let x = evt.pageX - offset.left; - let y = evt.pageY - offset.top; - - // clamp stop to the swatch - x = x < 0 ? 0 : x > MAX ? MAX : x; - y = y < 0 ? 0 : y > MAX ? MAX : y; - - draggingCoord.css('left', x).css('top', y); - - // calculate stop offset - const fracx = x / SIZEX; - const fracy = y / SIZEY; - - const type = draggingCoord.data('coord'); - const grd = curGradient; - - switch (type) { - case 'start': - attrInput.x1.val(fracx); - attrInput.y1.val(fracy); - grd.setAttribute('x1', fracx); - grd.setAttribute('y1', fracy); - break; - case 'end': - attrInput.x2.val(fracx); - attrInput.y2.val(fracy); - grd.setAttribute('x2', fracx); - grd.setAttribute('y2', fracy); - break; - case 'center': - attrInput.cx.val(fracx); - attrInput.cy.val(fracy); - grd.setAttribute('cx', fracx); - grd.setAttribute('cy', fracy); - cX = fracx; - cY = fracy; - xform(); - break; - case 'focus': - attrInput.fx.val(fracx); - attrInput.fy.val(fracy); - grd.setAttribute('fx', fracx); - grd.setAttribute('fy', fracy); - xform(); - } - - evt.preventDefault(); - }; - - const onCoordUp = function () { - draggingCoord = null; - $win.unbind('mousemove', onCoordDrag).unbind('mouseup', onCoordUp); - }; - - // Linear gradient - // (function () { - - stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); - - let numstops = stops.length; - // if there are not at least two stops, then - if (numstops < 2) { - while (numstops < 2) { - curGradient.append(document.createElementNS(ns.svg, 'stop')); - ++numstops; - } - stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); - } - - for (let i = 0; i < numstops; i++) { - mkStop(0, 0, 0, 0, stops[i]); - } - - spreadMethodOpt.val(curGradient.getAttribute('spreadMethod') || 'pad'); - - let offset; - - // No match, so show focus point - showFocus = false; - - previewRect.setAttribute('fill-opacity', gradalpha / 100); - - $wc('#' + id + ' div.grad_coord').mousedown(function (evt) { - evt.preventDefault(); - draggingCoord = $(this); - // const sPos = draggingCoord.offset(); - offset = draggingCoord.parent().offset(); - $win.mousemove(onCoordDrag).mouseup(onCoordUp); - }); - - // bind GUI elements - $wc('#' + id + '_jGraduate_Ok').bind('click', function () { - $this.paint.type = curType; - $this.paint[curType] = curGradient.cloneNode(true); - $this.paint.solidColor = null; - okClicked(); - }); - $wc('#' + id + '_jGraduate_Cancel').bind('click', function (paint) { - cancelClicked(); - }); - - if (curType === 'radialGradient') { - if (showFocus) { - focusCoord.show(); - } else { - focusCoord.hide(); - attrInput.fx.val(''); - attrInput.fy.val(''); - } - } - - $wc('#' + id + '_jGraduate_match_ctr')[0].checked = !showFocus; - - let lastfx, lastfy; - - $wc('#' + id + '_jGraduate_match_ctr').change(function () { - showFocus = !this.checked; - focusCoord.toggle(showFocus); - attrInput.fx.val(''); - attrInput.fy.val(''); - const grd = curGradient; - if (!showFocus) { - lastfx = grd.getAttribute('fx'); - lastfy = grd.getAttribute('fy'); - grd.removeAttribute('fx'); - grd.removeAttribute('fy'); - } else { - const fX = lastfx || 0.5; - const fY = lastfy || 0.5; - grd.setAttribute('fx', fX); - grd.setAttribute('fy', fY); - attrInput.fx.val(fX); - attrInput.fy.val(fY); - } - }); - - stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); - numstops = stops.length; - // if there are not at least two stops, then - if (numstops < 2) { - while (numstops < 2) { - curGradient.append(document.createElementNS(ns.svg, 'stop')); - ++numstops; - } - stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); - } - - let slider; - - const setSlider = function (e) { - const {offset: {left}} = slider; - const div = slider.parent; - let x = (e.pageX - left - Number.parseInt(div.css('border-left-width'))); - if (x > SLIDERW) x = SLIDERW; - if (x <= 0) x = 0; - const posx = x - 5; - x /= SLIDERW; - - switch (slider.type) { - case 'radius': - x = (x * 2) ** 2.5; - if (x > 0.98 && x < 1.02) x = 1; - if (x <= 0.01) x = 0.01; - curGradient.setAttribute('r', x); - break; - case 'opacity': - $this.paint.alpha = Number.parseInt(x * 100); - previewRect.setAttribute('fill-opacity', x); - break; - case 'ellip': - scaleX = 1; - scaleY = 1; - if (x < 0.5) { - x /= 0.5; // 0.001 - scaleX = x <= 0 ? 0.01 : x; - } else if (x > 0.5) { - x /= 0.5; // 2 - x = 2 - x; - scaleY = x <= 0 ? 0.01 : x; - } - xform(); - x -= 1; - if (scaleY === x + 1) { - x = Math.abs(x); - } - break; - case 'angle': - x -= 0.5; - angle = x *= 180; - xform(); - x /= 100; - break; - } - slider.elem.css({'margin-left': posx}); - x = Math.round(x * 100); - slider.input.val(x); - }; - - let ellipVal = 0, angleVal = 0; - - if (curType === 'radialGradient') { - const tlist = curGradient.gradientTransform.baseVal; - if (tlist.numberOfItems === 2) { - const t = tlist.getItem(0); - const s = tlist.getItem(1); - if (t.type === 2 && s.type === 3) { - const m = s.matrix; - if (m.a !== 1) { - ellipVal = Math.round(-(1 - m.a) * 100); - } else if (m.d !== 1) { - ellipVal = Math.round((1 - m.d) * 100); - } - } - } else if (tlist.numberOfItems === 3) { - // Assume [R][T][S] - const r = tlist.getItem(0); - const t = tlist.getItem(1); - const s = tlist.getItem(2); - - if (r.type === 4 && - t.type === 2 && - s.type === 3 - ) { - angleVal = Math.round(r.angle); - const m = s.matrix; - if (m.a !== 1) { - ellipVal = Math.round(-(1 - m.a) * 100); - } else if (m.d !== 1) { - ellipVal = Math.round((1 - m.d) * 100); - } - } - } - } - - const sliders = { - radius: { - handle: '#' + id + '_jGraduate_RadiusArrows', - input: '#' + id + '_jGraduate_RadiusInput', - val: (curGradient.getAttribute('r') || 0.5) * 100 - }, - opacity: { - handle: '#' + id + '_jGraduate_OpacArrows', - input: '#' + id + '_jGraduate_OpacInput', - val: $this.paint.alpha || 100 - }, - ellip: { - handle: '#' + id + '_jGraduate_EllipArrows', - input: '#' + id + '_jGraduate_EllipInput', - val: ellipVal - }, - angle: { - handle: '#' + id + '_jGraduate_AngleArrows', - input: '#' + id + '_jGraduate_AngleInput', - val: angleVal - } - }; - - $.each(sliders, function (type, data) { - const handle = $(data.handle); - handle.mousedown(function (evt) { - const parent = handle.parent(); - slider = { - type, - elem: handle, - input: $(data.input), - parent, - offset: parent.offset() - }; - $win.mousemove(dragSlider).mouseup(stopSlider); - evt.preventDefault(); - }); - - $(data.input).val(data.val).change(function () { - const isRad = curType === 'radialGradient'; - let val = Number(this.value); - let xpos = 0; - switch (type) { - case 'radius': - if (isRad) curGradient.setAttribute('r', val / 100); - xpos = (((val / 100) ** (1 / 2.5)) / 2) * SLIDERW; - break; - - case 'opacity': - $this.paint.alpha = val; - previewRect.setAttribute('fill-opacity', val / 100); - xpos = val * (SLIDERW / 100); - break; - - case 'ellip': - scaleX = scaleY = 1; - if (val === 0) { - xpos = SLIDERW * 0.5; - break; - } - if (val > 99.5) val = 99.5; - if (val > 0) { - scaleY = 1 - (val / 100); - } else { - scaleX = -(val / 100) - 1; - } - - xpos = SLIDERW * ((val + 100) / 2) / 100; - if (isRad) xform(); - break; - - case 'angle': - angle = val; - xpos = angle / 180; - xpos += 0.5; - xpos *= SLIDERW; - if (isRad) xform(); - } - if (xpos > SLIDERW) { - xpos = SLIDERW; - } else if (xpos < 0) { - xpos = 0; - } - handle.css({'margin-left': xpos - 5}); - }).change(); - }); - - const dragSlider = function (evt) { - setSlider(evt); - evt.preventDefault(); - }; - - const stopSlider = function (evt) { - $win.unbind('mousemove', dragSlider).unbind('mouseup', stopSlider); - slider = null; - }; - - // -------------- - let thisAlpha = ($this.paint.alpha * 255 / 100).toString(16); - while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } - thisAlpha = thisAlpha.split('.')[0]; - color = $this.paint.solidColor === 'none' ? '' : $this.paint.solidColor + thisAlpha; - - if (!isSolid) { - color = stops[0].getAttribute('stop-color'); - } - - // This should be done somewhere else, probably - $.extend($.fn.jPicker.defaults.window, { - alphaSupport: true, effects: {type: 'show', speed: 0} - }); - - colPicker.jPicker( - { - window: {title: $settings.window.pickerTitle}, - images: {clientPath: $settings.images.clientPath}, - color: {active: color, alphaSupport: true} - }, - function (clr) { - $this.paint.type = 'solidColor'; - $this.paint.alpha = clr.val('ahex') ? Math.round((clr.val('a') / 255) * 100) : 100; - $this.paint.solidColor = clr.val('hex') ? clr.val('hex') : 'none'; - $this.paint.radialGradient = null; - okClicked(); - }, - null, - function () { cancelClicked(); } - ); - // JFH !!!! - // const tabs = $wc(idref + ' .jGraduate_tabs li'); - const tabs = $wc('.jGraduate_tabs li'); - tabs.click(function () { - tabs.removeClass('jGraduate_tab_current'); - $(this).addClass('jGraduate_tab_current'); - $wc(idref + ' > div').hide(); - const type = $(this).attr('data-type'); - $wc(idref + ' .jGraduate_gradPick').show(); - if (type === 'rg' || type === 'lg') { - // Show/hide appropriate fields - $wc('.jGraduate_' + type + '_field').show(); - $wc('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field').hide(); - - $wc('#' + id + '_jgraduate_rect')[0] - .setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)'); - - // Copy stops - - curType = type === 'lg' ? 'linearGradient' : 'radialGradient'; - - $wc('#' + id + '_jGraduate_OpacInput').val($this.paint.alpha).change(); - - const newGrad = $wc('#' + id + '_' + type + '_jgraduate_grad')[0]; - - if (curGradient !== newGrad) { - const curStops = $(curGradient).find('stop'); - $(newGrad).empty().append(curStops); - curGradient = newGrad; - const sm = spreadMethodOpt.val(); - curGradient.setAttribute('spreadMethod', sm); - } - showFocus = type === 'rg' && curGradient.getAttribute('fx') !== null && !(cx === fx && cy === fy); - $wc('#' + id + '_jGraduate_focusCoord').toggle(showFocus); - if (showFocus) { - $wc('#' + id + '_jGraduate_match_ctr')[0].checked = false; - } - } else { - $wc(idref + ' .jGraduate_gradPick').hide(); - $wc(idref + ' .jGraduate_colPick').show(); - } - }); - $wc(idref + ' > div').hide(); - tabs.removeClass('jGraduate_tab_current'); - let tab; - switch ($this.paint.type) { - case 'linearGradient': - tab = $wc(idref + ' .jGraduate_tab_lingrad'); - break; - case 'radialGradient': - tab = $wc(idref + ' .jGraduate_tab_radgrad'); - break; - default: - tab = $wc(idref + ' .jGraduate_tab_color'); - break; - } - $this.show(); - - // jPicker will try to show after a 0ms timeout, so need to fire this after that - setTimeout(() => { - tab.addClass('jGraduate_tab_current').click(); - }, 10); + newstop: 'inverse' // same, inverse, black, white +}; + +const isGecko = navigator.userAgent.includes('Gecko/'); + +/** +* @typedef {PlainObject} module:jGraduate.Attrs +*/ +/** +* @param {SVGElement} elem +* @param {module:jGraduate.Attrs} attrs +* @returns {void} +*/ +function setAttrs (elem, attrs) { + if (isGecko) { + Object.entries(attrs).forEach(([aname, val]) => { + elem.setAttribute(aname, val); }); - }; - return $; + } else { + Object.entries(attrs).forEach(([aname, val]) => { + const prop = elem[aname]; + if (prop && prop.constructor === 'SVGLength') { + prop.baseVal.value = val; + } else { + elem.setAttribute(aname, val); + } + }); + } +} + +/** +* @param {string} name +* @param {module:jGraduate.Attrs} attrs +* @param {Element} newparent +* @returns {SVGElement} +*/ +function mkElem (name, attrs, newparent) { + const elem = document.createElementNS(ns.svg, name); + setAttrs(elem, attrs); + if (newparent) { + newparent.append(elem); + } + return elem; +} + +function deepExtend(out) { + out = out || {}; + + for (let i = 1, len = arguments.length; i < len; ++i) { + let obj = arguments[i]; + + if (!obj) { + continue; + } + + for (const key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + + // based on https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ + if (Object.prototype.toString.call(obj[key]) === '[object Object]') { + out[key] = deepExtend(out[key], obj[key]); + continue; + } + + out[key] = obj[key]; + } + } + + return out; +} + +/** +* @typedef {PlainObject} module:jGraduate.ColorOpac Object may have one or both values +* @property {string} [color] #Hex color +* @property {Float} [opac] 0-1 +*/ +/** +* @typedef {PlainObject} module:jGraduate.Options +* @property {module:jGraduate~Paint} [paint] A Paint object object describing the paint to display initially; defaults to a new instance without options (defaults to opaque white) +* @property {external:Window} [window] +* @property {string} [window.pickerTitle="Drag markers to pick a paint"] +* @property {PlainObject} [images] +* @property {string} [images.clientPath="images/"] +* @property {"same"|"inverse"|"black"|"white"|module:jGraduate.ColorOpac} [newstop="inverse"] +*/ + +/** +* @callback external:jQuery.fn.jGraduate.OkCallback +* @param {external:jQuery.jGraduate.Paint} paint +* @returns {void} +*/ +/** +* @callback external:jQuery.fn.jGraduate.CancelCallback +* @returns {void} +*/ + +/** +* @function external:jQuery.fn.jGraduate +* @param {module:jGraduate.Options} [options] +* @param {external:jQuery.fn.jGraduate.OkCallback} [okCallback] Called with a Paint object when Ok is pressed +* @param {external:jQuery.fn.jGraduate.CancelCallback} [cancelCallback] Called with no arguments when Cancel is pressed +* @returns {external:jQuery} +*/ +export function jGraduateMethod (elem, options, okCallback, cancelCallback) { + const $this = elem, + $settings = Object.assign({}, jGraduateDefaults, options || {}), + id = $this.getAttribute('id'), + idref = '#' + $this.getAttribute('id') + ' '; + // JFH !!!!! + const $shadowRoot = elem.parentNode; + // const $wc = (selector) => $($shadowRoot.querySelectorAll(selector)); + + if (!idref) { + // eslint-disable-next-line no-alert + alert('Container element must have an id attribute to maintain unique id strings for sub-elements.'); + return; + } + + const okClicked = function () { + switch ($this.paint.type) { + case 'radialGradient': + $this.paint.linearGradient = null; + break; + case 'linearGradient': + $this.paint.radialGradient = null; + break; + case 'solidColor': + $this.paint.radialGradient = $this.paint.linearGradient = null; + break; + } + typeof $this.okCallback === 'function' && $this.okCallback($this.paint); + $this.style.display = 'none'; + }; + const cancelClicked = function () { + typeof $this.cancelCallback === 'function' && $this.cancelCallback(); + $this.style.display = 'none'; + }; + Object.assign($this, { + // make a copy of the incoming paint + paint: new jGraduate.Paint({copy: $settings.paint}), + okCallback: typeof okCallback === 'function' ? okCallback : null, + cancelCallback: typeof cancelCallback === 'function' ? cancelCallback : null + }); + + let // pos = $this.position(), + color = null; + const $win = window; + + if ($this.paint.type === 'none') { + $this.paint = new jGraduate.Paint({solidColor: 'ffffff'}); + } + $this.classList.add('jGraduate_Picker'); + /* eslint-disable max-len */ + $this.innerHTML = '
    ' + + '
  • Solid Color
  • ' + + '
  • Linear Gradient
  • ' + + '
  • Radial Gradient
  • ' + + '
' + + '
' + + '
' + + '
' + + '
'; + /* JFH !!!! */ + const colPicker = $this.querySelector('#jGraduate_colPick'); + const gradPicker = $this.querySelector('#jGraduate_gradPick'); + const html = '
' + + '

' + $settings.window.pickerTitle + '

' + + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '
' + + '' + + '' + + '
'; + const div = document.createElement('div'); + div.innerHTML = html; + while (div.children.length > 0) { + gradPicker.appendChild(div.children[0]); + } + /* eslint-enable max-len */ + // -------------- + // Set up all the SVG elements (the gradient, stops and rectangle) + const MAX = 256, + MARGINX = 0, + MARGINY = 0, + // STOP_RADIUS = 15 / 2, + SIZEX = MAX - 2 * MARGINX, + SIZEY = MAX - 2 * MARGINY; + + const attrInput = {}; + + const SLIDERW = 145; + const JQSliderBars = $this.querySelectorAll('.jGraduate_SliderBar'); + for (const JQSliderBar of JQSliderBars) { + JQSliderBar.style.width = SLIDERW + 'px'; + } + // JFH !!!!!! + const container = $this.querySelector('#' + id + '_jGraduate_GradContainer'); + + const svg = mkElem('svg', { + id: id + '_jgraduate_svg', + width: MAX, + height: MAX, + xmlns: ns.svg + }, container); + + // This wasn't working as designed + // let curType; + // curType = curType || $this.paint.type; + + // if we are sent a gradient, import it + let curType = $this.paint.type; + + let grad = $this.paint[curType]; + let curGradient = grad; + + const gradalpha = $this.paint.alpha; + + const isSolid = curType === 'solidColor'; + + // Make any missing gradients + switch (curType) { + case 'solidColor': + // fall through + case 'linearGradient': + if (!isSolid) { + curGradient.id = id + '_lg_jgraduate_grad'; + grad = curGradient = svg.appendChild(curGradient); + } + mkElem('radialGradient', { + id: id + '_rg_jgraduate_grad' + }, svg); + if (curType === 'linearGradient') { break; } + // fall through + case 'radialGradient': + if (!isSolid) { + curGradient.id = id + '_rg_jgraduate_grad'; + grad = curGradient = svg.appendChild(curGradient); + } + mkElem('linearGradient', { + id: id + '_lg_jgraduate_grad' + }, svg); + } + + let stopGroup; // eslint-disable-line prefer-const + if (isSolid) { + // JFH !!!!!!!! + grad = curGradient = $this.querySelector('#' + id + '_lg_jgraduate_grad'); + color = $this.paint[curType]; + mkStop(0, '#' + color, 1); + + const type = typeof $settings.newstop; + + if (type === 'string') { + switch ($settings.newstop) { + case 'same': + mkStop(1, '#' + color, 1); + break; + + case 'inverse': { + // Invert current color for second stop + let inverted = ''; + for (let i = 0; i < 6; i += 2) { + // const ch = color.substr(i, 2); + let inv = (255 - Number.parseInt(color.substr(i, 2), 16)).toString(16); + if (inv.length < 2) inv = 0 + inv; + inverted += inv; + } + mkStop(1, '#' + inverted, 1); + break; + } case 'white': + mkStop(1, '#ffffff', 1); + break; + + case 'black': + mkStop(1, '#000000', 1); + break; + } + } else if (type === 'object') { + const opac = ('opac' in $settings.newstop) ? $settings.newstop.opac : 1; + mkStop(1, ($settings.newstop.color || '#' + color), opac); + } + } + + const x1 = Number.parseFloat(grad.getAttribute('x1') || 0.0), + y1 = Number.parseFloat(grad.getAttribute('y1') || 0.0), + x2 = Number.parseFloat(grad.getAttribute('x2') || 1.0), + y2 = Number.parseFloat(grad.getAttribute('y2') || 0.0); + + const cx = Number.parseFloat(grad.getAttribute('cx') || 0.5), + cy = Number.parseFloat(grad.getAttribute('cy') || 0.5), + fx = Number.parseFloat(grad.getAttribute('fx') || cx), + fy = Number.parseFloat(grad.getAttribute('fy') || cy); + + const previewRect = mkElem('rect', { + id: id + '_jgraduate_rect', + x: MARGINX, + y: MARGINY, + width: SIZEX, + height: SIZEY, + fill: 'url(#' + id + '_jgraduate_grad)', + 'fill-opacity': gradalpha / 100 + }, svg); + + // stop visuals created here + const beginCoord = document.createElement('div'); + beginCoord.setAttribute('class', 'grad_coord jGraduate_lg_field'); + beginCoord.setAttribute('title', 'Begin Stop'); + beginCoord.textContent = 1; + beginCoord.style.top = y1 * MAX; + beginCoord.style.left = x1 * MAX; + beginCoord.dataset.coord = 'start'; + container.appendChild(beginCoord); + + const endCoord = document.createElement('div'); + endCoord.setAttribute('class', 'grad_coord jGraduate_lg_field'); + endCoord.setAttribute('title', 'End stop'); + endCoord.textContent = 2; + endCoord.style.top = y2 * MAX; + endCoord.style.left = x2 * MAX; + endCoord.dataset.coord = 'end'; + container.appendChild(endCoord); + + const centerCoord = document.createElement('div'); + centerCoord.setAttribute('class', 'grad_coord jGraduate_rg_field'); + centerCoord.setAttribute('title', 'Center stop'); + centerCoord.textContent = 'C'; + centerCoord.style.top = cy * MAX; + centerCoord.style.left = cx * MAX; + centerCoord.dataset.coord = 'center'; + container.appendChild(centerCoord); + + const focusCoord = document.createElement('div'); + focusCoord.setAttribute('class', 'grad_coord jGraduate_rg_field'); + focusCoord.setAttribute('title', 'Focus point'); + focusCoord.textContent = 'F'; + focusCoord.style.top = fy * MAX; + focusCoord.style.left = fx * MAX; + focusCoord.style.display = 'none'; + focusCoord.dataset.coord = 'focus'; + focusCoord.setAttribute('id', id + '_jGraduate_focusCoord'); + container.appendChild(focusCoord); + + let showFocus; + const onAttrChangeHandler = (e, attr, isRadial) => { + // TODO: Support values < 0 and > 1 (zoomable preview?) + if (isNaN(Number.parseFloat(e.target.value)) || e.target.value < 0) { + e.target.value = 0.0; + } else if (e.target.value > 1) { + e.target.value = 1.0; + } + + if (!(attr[0] === 'f' && + !showFocus) && + ((isRadial && curType === 'radialGradient') || (!isRadial && curType === 'linearGradient'))) { + curGradient.setAttribute(attr, e.target.value); + } + + const $elem = isRadial + ? attr[0] === 'c' ? centerCoord : focusCoord + : attr[1] === '1' ? beginCoord : endCoord; + + if (attr.includes('x') === 'left') { + $elem.style.left = e.target.value * MAX; + } else if (attr.includes('x') === 'top') { + $elem.style.top = e.target.value * MAX; + } + }; + for (const [i, attr] of ['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'].entries()) { + const isRadial = isNaN(attr[1]); + + let attrval = curGradient.getAttribute(attr); + if (!attrval) { + // Set defaults + if (isRadial) { + // For radial points + attrval = '0.5'; + } else { + // Only x2 is 1 + attrval = attr === 'x2' ? '1.0' : '0.0'; + } + } + + attrInput[attr] = $this.querySelector('#' + id + '_jGraduate_' + attr); + attrInput[attr].value = attrval; + attrInput[attr].addEventListener('change', (evt) => onAttrChangeHandler(evt, attr, isRadial)); + attrInput[attr].dispatchEvent(new Event('change')); + } + + /** + * + * @param {Float} n + * @param {Float|string} colr + * @param {Float} opac + * @param {boolean} [sel] + * @param {SVGStopElement} [stopElem] + * @returns {SVGStopElement} + */ + function mkStop (n, colr, opac, sel, stopElem) { + const stop = stopElem || mkElem('stop', { + id: 'jq_stop_' + Math.floor((Math.random() * 10000) + 1), + 'stop-color': colr, + 'stop-opacity': opac, + offset: n + }, curGradient); + if (stopElem) { + colr = stopElem.getAttribute('stop-color'); + opac = stopElem.getAttribute('stop-opacity'); + n = stopElem.getAttribute('offset'); + } else { + curGradient.appendChild(stop); + } + if (opac === null) opac = 1; + + const pickerD = 'M-6.2,0.9c3.6-4,6.7-4.3,6.7-12.4c-0.2,7.9,' + + '3.1,8.8,6.5,12.4c3.5,3.8,2.9,9.6,0,12.3c-3.1,2.8-10.4,' + + '2.7-13.2,0C-9.6,9.9-9.4,4.4-6.2,0.9z'; + + const pathbg = mkElem('path', { + id: 'jq_pathbg_' + Math.floor((Math.random() * 10000) + 1), + d: pickerD, + fill: 'url(#jGraduate_trans)', + transform: 'translate(' + (10 + n * MAX) + ', 26)' + }, stopGroup); + + const path = mkElem('path', { + d: pickerD, + fill: colr, + 'fill-opacity': opac, + transform: 'translate(' + (10 + n * MAX) + ', 26)', + stroke: '#000', + 'stroke-width': 1.5 + }, stopGroup); + + path.addEventListener('mousedown', function (e) { + selectStop(this); + drag = curStop; + $win.addEventListener('mousemove', dragColor); + $win.addEventListener('mouseup', remDrags); + stopOffset = findPos(stopMakerDiv); + e.preventDefault(); + return false; + }); + path.dataset.stop = stop.getAttribute('id'); + path.dataset.bg = pathbg.getAttribute('id'); + path.addEventListener('dblclick', function () { + $this.querySelector('#jGraduate_LightBox').style.display = 'block'; + const colorhandle = this; + let stopOpacity = Number(stop.getAttribute('stop-opacity')) || 1; + let stopColor = stop.getAttribute('stop-color') || 1; + let thisAlpha = (Number.parseFloat(stopOpacity) * 255).toString(16); + while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } + colr = stopColor.substr(1) + thisAlpha; + const jqPickerElem = $this.querySelector('#' + id + '_jGraduate_stopPicker'); + jqPickerElem.style.left = '100px'; + jqPickerElem.style.bottom = '15px'; + jPickerMethod(jqPickerElem, { + window: {title: 'Pick the start color and opacity for the gradient'}, + images: {clientPath: $settings.images.clientPath}, + color: {active: colr, alphaSupport: true} + }, function (clr, arg2) { + stopColor = clr.val('hex') ? ('#' + clr.val('hex')) : 'none'; + stopOpacity = clr.val('a') !== null ? clr.val('a') / 256 : 1; + colorhandle.setAttribute('fill', stopColor); + colorhandle.setAttribute('fill-opacity', stopOpacity); + stop.setAttribute('stop-color', stopColor); + stop.setAttribute('stop-opacity', stopOpacity); + $this.querySelector('#jGraduate_LightBox').style.display = 'none'; + $this.querySelector('#' + id + '_jGraduate_stopPicker').style.display = 'none'; + }, null, function () { + $this.querySelector('#jGraduate_LightBox').style.display = 'none'; + $this.querySelector('#' + id + '_jGraduate_stopPicker').style.display = 'none'; + }); + }); + const jqStopEls = curGradient.querySelectorAll('stop'); + for (const jqStopEl of jqStopEls) { + const curS = jqStopEl; + if (Number(jqStopEl.getAttribute('offset')) > n) { + if (!colr) { + const newcolor = jqStopEl.getAttribute('stop-color'); + const newopac = jqStopEl.getAttribute('stop-opacity'); + stop.setAttribute('stop-color', newcolor); + path.setAttribute('fill', newcolor); + stop.setAttribute('stop-opacity', newopac === null ? 1 : newopac); + path.setAttribute('fill-opacity', newopac === null ? 1 : newopac); + } + curS.insertAdjacentElement('beforebegin', stop); + // curS.before(stop); + // return false; + } + // return true; + } + if (sel) selectStop(path); + return stop; + } + + /** + * + * @returns {void} + */ + function remStop () { + delStop.setAttribute('display', 'none'); + const path = curStop; + delete path.dataset.stop; + delete path.dataset.bg; + curStop.parentNode.removeChild(curStop); + } + + const stopMakerDiv = $this.querySelector('#' + id + '_jGraduate_StopSlider'); + + let stops, curStop, drag; + + const delStop = mkElem('path', { + d: 'm9.75,-6l-19.5,19.5m0,-19.5l19.5,19.5', + fill: 'none', + stroke: '#D00', + 'stroke-width': 5, + display: 'none' + }, undefined); // stopMakerSVG); + + /** + * @param {Element} item + * @returns {void} + */ + function selectStop (item) { + if (curStop) curStop.setAttribute('stroke', '#000'); + item.setAttribute('stroke', 'blue'); + curStop = item; + } + + let stopOffset; + + /** + * + * @returns {void} + */ + function remDrags () { + $win.removeEventListener('mousemove', dragColor); + if (delStop.getAttribute('display') !== 'none') { + remStop(); + } + drag = null; + } + + let scaleX = 1, scaleY = 1, angle = 0; + + let cX = cx; + let cY = cy; + /** + * + * @returns {void} + */ + function xform () { + const rot = angle ? 'rotate(' + angle + ',' + cX + ',' + cY + ') ' : ''; + if (scaleX === 1 && scaleY === 1) { + curGradient.removeAttribute('gradientTransform'); + // $wc('#ang').addClass('dis'); + } else { + const x = -cX * (scaleX - 1); + const y = -cY * (scaleY - 1); + curGradient.setAttribute( + 'gradientTransform', + rot + 'translate(' + x + ',' + y + ') scale(' + + scaleX + ',' + scaleY + ')' + ); + // $wc('#ang').removeClass('dis'); + } + } + + /** + * @param {Event} evt + * @returns {void} + */ + function dragColor (evt) { + let x = evt.pageX - stopOffset.left; + const y = evt.pageY - stopOffset.top; + x = x < 10 + ? 10 + : x > MAX + 10 + ? MAX + 10 + : x; + + const xfStr = 'translate(' + x + ', 26)'; + if (y < -60 || y > 130) { + delStop.setAttribute('display', 'block'); + delStop.setAttribute('transform', xfStr); + } else { + delStop.setAttribute('display', 'none'); + } + + drag.setAttribute('transform', xfStr); + const jqpgpath = $this.querySelector('#'+drag.dataset.bg); + jqpgpath.setAttribute('transform', xfStr); + const stop = $this.querySelector('#'+drag.dataset.stop); + const sX = (x - 10) / MAX; + + stop.setAttribute('offset', sX); + + let last = 0; + const jqStopElems = curGradient.querySelectorAll('stop'); + [].forEach.call(jqStopElems, function (jqStopElem) { + const cur = jqStopElem.getAttribute('offset'); + const t = jqStopElem; + if (cur < last) { + t.previousElementSibling.insertAdjacentElement('beforebegin', t); + stops = curGradient.querySelectorAll('stop'); + } + last = cur; + }); + } + + const stopMakerSVG = mkElem('svg', { + width: '100%', + height: 45 + }, stopMakerDiv); + + const transPattern = mkElem('pattern', { + width: 16, + height: 16, + patternUnits: 'userSpaceOnUse', + id: 'jGraduate_trans' + }, stopMakerSVG); + + const transImg = mkElem('image', { + width: 16, + height: 16 + }, transPattern); + + const bgImage = $settings.images.clientPath + 'map-opacity.png'; + + transImg.setAttributeNS(ns.xlink, 'xlink:href', bgImage); + + stopMakerSVG.addEventListener('click', function (evt) { + stopOffset = findPos(stopMakerDiv); + const {target} = evt; + if (target.tagName === 'path') return; + let x = evt.pageX - stopOffset.left - 8; + x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x; + mkStop(x / MAX, 0, 0, true); + evt.stopPropagation(); + }); + + stopMakerSVG.addEventListener('mouseover', function () { + stopMakerSVG.append(delStop); + }); + + stopGroup = mkElem('g', {}, stopMakerSVG); + + mkElem('line', { + x1: 10, + y1: 15, + x2: MAX + 10, + y2: 15, + 'stroke-width': 2, + stroke: '#000' + }, stopMakerSVG); + const spreadMethodOpt = gradPicker.querySelector('#jGraduate_spreadMethod'); + spreadMethodOpt.addEventListener('change', function () { + curGradient.setAttribute('spreadMethod', this.value); + }); + + // handle dragging the stop around the swatch + let draggingCoord = null; + + const onCoordDrag = function (evt) { + let x = evt.pageX - offset.left; + let y = evt.pageY - offset.top; + + // clamp stop to the swatch + x = x < 0 ? 0 : x > MAX ? MAX : x; + y = y < 0 ? 0 : y > MAX ? MAX : y; + + draggingCoord.style.left = x + 'px'; + draggingCoord.style.top = y + 'px'; + + // calculate stop offset + const fracx = x / SIZEX; + const fracy = y / SIZEY; + + const type = draggingCoord.dataset.coord; + const grd = curGradient; + + switch (type) { + case 'start': + attrInput.x1.value = fracx; + attrInput.y1.value = fracy; + grd.setAttribute('x1', fracx); + grd.setAttribute('y1', fracy); + break; + case 'end': + attrInput.x2.value = fracx; + attrInput.y2.value = fracy; + grd.setAttribute('x2', fracx); + grd.setAttribute('y2', fracy); + break; + case 'center': + attrInput.cx.value = fracx; + attrInput.cy.value = fracy; + grd.setAttribute('cx', fracx); + grd.setAttribute('cy', fracy); + cX = fracx; + cY = fracy; + xform(); + break; + case 'focus': + attrInput.fx.value = fracx; + attrInput.fy.value = fracy; + grd.setAttribute('fx', fracx); + grd.setAttribute('fy', fracy); + xform(); + } + + evt.preventDefault(); + }; + + const onCoordUp = function () { + draggingCoord = null; + $win.removeEventListener('mousemove', onCoordDrag); + $win.removeEventListener('mouseup', onCoordUp); + }; + + // Linear gradient + // (function () { + + stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); + + let numstops = stops.length; + // if there are not at least two stops, then + if (numstops < 2) { + while (numstops < 2) { + curGradient.append(document.createElementNS(ns.svg, 'stop')); + ++numstops; + } + stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); + } + + for (let i = 0; i < numstops; i++) { + mkStop(0, 0, 0, 0, stops[i]); + } + + spreadMethodOpt.setAttribute('value', curGradient.getAttribute('spreadMethod') || 'pad'); + + let offset; + + // No match, so show focus point + showFocus = false; + + previewRect.setAttribute('fill-opacity', gradalpha / 100); + + const JQGradCoords = $this.querySelectorAll('#' + id + ' div.grad_coord'); + const onMouseDownGradCoords = (e) => { + e.preventDefault(); + draggingCoord = e.target; + offset = findPos(draggingCoord.parentNode); + $win.addEventListener('mousemove', onCoordDrag); + $win.addEventListener('mouseup', onCoordUp); + }; + for (const JQGradCoord of JQGradCoords) { + JQGradCoord.addEventListener('mousedown', onMouseDownGradCoords); + } + + // bind GUI elements + $this.querySelector('#' + id + '_jGraduate_Ok').addEventListener('click', function () { + $this.paint.type = curType; + $this.paint[curType] = curGradient.cloneNode(true); + $this.paint.solidColor = null; + okClicked(); + }); + $this.querySelector('#' + id + '_jGraduate_Cancel').addEventListener('click', cancelClicked); + + if (curType === 'radialGradient') { + if (showFocus) { + focusCoord.style.display = 'block'; + } else { + focusCoord.style.display = 'none'; + attrInput.fx.value = ''; + attrInput.fy.value = ''; + } + } + + $this.querySelector('#' + id + '_jGraduate_match_ctr').checked = !showFocus; + + let lastfx, lastfy; + const onMatchCtrHandler = (e) => { + showFocus = !e.target.checked; + if (showFocus) { + focusCoord.style.display = 'block'; + } else { + focusCoord.style.display = 'none'; + } + attrInput.fx.value = ''; + attrInput.fy.value = ''; + const grd = curGradient; + if (!showFocus) { + lastfx = grd.getAttribute('fx'); + lastfy = grd.getAttribute('fy'); + grd.removeAttribute('fx'); + grd.removeAttribute('fy'); + } else { + const fX = lastfx || 0.5; + const fY = lastfy || 0.5; + grd.setAttribute('fx', fX); + grd.setAttribute('fy', fY); + attrInput.fx.value = fX; + attrInput.fy.value = fY; + } + }; + $this.querySelector('#' + id + '_jGraduate_match_ctr').addEventListener('change', onMatchCtrHandler); + stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); + numstops = stops.length; + // if there are not at least two stops, then + if (numstops < 2) { + while (numstops < 2) { + curGradient.append(document.createElementNS(ns.svg, 'stop')); + ++numstops; + } + stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop'); + } + + let slider; + + const setSlider = function (e) { + const {offset: {left}} = slider; + const divi = slider.parent; + let x = (e.pageX - left - Number.parseInt(getComputedStyle(divi, null).getPropertyValue('border-left-width'))); + if (x > SLIDERW) x = SLIDERW; + if (x <= 0) x = 0; + const posx = x - 5; + x /= SLIDERW; + + switch (slider.type) { + case 'radius': + x = (x * 2) ** 2.5; + if (x > 0.98 && x < 1.02) x = 1; + if (x <= 0.01) x = 0.01; + curGradient.setAttribute('r', x); + break; + case 'opacity': + $this.paint.alpha = Number.parseInt(x * 100); + previewRect.setAttribute('fill-opacity', x); + break; + case 'ellip': + scaleX = 1; + scaleY = 1; + if (x < 0.5) { + x /= 0.5; // 0.001 + scaleX = x <= 0 ? 0.01 : x; + } else if (x > 0.5) { + x /= 0.5; // 2 + x = 2 - x; + scaleY = x <= 0 ? 0.01 : x; + } + xform(); + x -= 1; + if (scaleY === x + 1) { + x = Math.abs(x); + } + break; + case 'angle': + x -= 0.5; + angle = x *= 180; + xform(); + x /= 100; + break; + } + slider.elem.style.marginLeft = posx + 'px'; + x = Math.round(x * 100); + slider.input.value = x; + }; + + let ellipVal = 0, angleVal = 0; + + if (curType === 'radialGradient') { + const tlist = curGradient.gradientTransform.baseVal; + if (tlist.numberOfItems === 2) { + const t = tlist.getItem(0); + const s = tlist.getItem(1); + if (t.type === 2 && s.type === 3) { + const m = s.matrix; + if (m.a !== 1) { + ellipVal = Math.round(-(1 - m.a) * 100); + } else if (m.d !== 1) { + ellipVal = Math.round((1 - m.d) * 100); + } + } + } else if (tlist.numberOfItems === 3) { + // Assume [R][T][S] + const r = tlist.getItem(0); + const t = tlist.getItem(1); + const s = tlist.getItem(2); + + if (r.type === 4 && + t.type === 2 && + s.type === 3 + ) { + angleVal = Math.round(r.angle); + const m = s.matrix; + if (m.a !== 1) { + ellipVal = Math.round(-(1 - m.a) * 100); + } else if (m.d !== 1) { + ellipVal = Math.round((1 - m.d) * 100); + } + } + } + } + const sliders = { + radius: { + handle: '#' + id + '_jGraduate_RadiusArrows', + input: '#' + id + '_jGraduate_RadiusInput', + val: (curGradient.getAttribute('r') || 0.5) * 100 + }, + opacity: { + handle: '#' + id + '_jGraduate_OpacArrows', + input: '#' + id + '_jGraduate_OpacInput', + val: $this.paint.alpha || 100 + }, + ellip: { + handle: '#' + id + '_jGraduate_EllipArrows', + input: '#' + id + '_jGraduate_EllipInput', + val: ellipVal + }, + angle: { + handle: '#' + id + '_jGraduate_AngleArrows', + input: '#' + id + '_jGraduate_AngleInput', + val: angleVal + } + }; + for (const [index, [type, data]] of Object.entries(Object.entries(sliders))) { + const handle = $this.querySelector(data.handle); + const sInput = $this.querySelector(data.input); + handle.addEventListener('mousedown', function (evt) { + const parent = handle.parentNode; + slider = { + type, + elem: handle, + input: sInput, + parent, + offset: findPos(parent) + }; + $win.addEventListener('mousemove', dragSlider); + $win.addEventListener('mouseup', stopSlider); + evt.preventDefault(); + }); + sInput.value = data.val; + sInput.addEventListener('change', function () { + const isRad = curType === 'radialGradient'; + let val = Number(this.value); + let xpos = 0; + switch (type) { + case 'radius': + if (isRad) curGradient.setAttribute('r', val / 100); + xpos = (((val / 100) ** (1 / 2.5)) / 2) * SLIDERW; + break; + + case 'opacity': + $this.paint.alpha = val; + previewRect.setAttribute('fill-opacity', val / 100); + xpos = val * (SLIDERW / 100); + break; + + case 'ellip': + scaleX = scaleY = 1; + if (val === 0) { + xpos = SLIDERW * 0.5; + break; + } + if (val > 99.5) val = 99.5; + if (val > 0) { + scaleY = 1 - (val / 100); + } else { + scaleX = -(val / 100) - 1; + } + + xpos = SLIDERW * ((val + 100) / 2) / 100; + if (isRad) xform(); + break; + + case 'angle': + angle = val; + xpos = angle / 180; + xpos += 0.5; + xpos *= SLIDERW; + if (isRad) xform(); + } + if (xpos > SLIDERW) { + xpos = SLIDERW; + } else if (xpos < 0) { + xpos = 0; + } + handle.style.marginLeft = (xpos - 5) + 'px'; + }); + sInput.dispatchEvent(new Event('change')); + } + + const dragSlider = function (evt) { + setSlider(evt); + evt.preventDefault(); + }; + + const stopSlider = function (evt) { + $win.removeEventListener('mousemove', dragSlider); + $win.removeEventListener('mouseup', stopSlider); + slider = null; + }; + + // -------------- + let thisAlpha = ($this.paint.alpha * 255 / 100).toString(16); + while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; } + thisAlpha = thisAlpha.split('.')[0]; + color = $this.paint.solidColor === 'none' ? '' : $this.paint.solidColor + thisAlpha; + + if (!isSolid) { + color = stops[0].getAttribute('stop-color'); + } + // This should be done somewhere else, probably + Object.assign(jPickerDefaults.window, { + alphaSupport: true, effects: {type: 'show', speed: 0} + }); + + jPickerMethod( + colPicker, + { + window: {title: $settings.window.pickerTitle}, + images: {clientPath: $settings.images.clientPath}, + color: {active: color, alphaSupport: true} + }, + function (clr) { + $this.paint.type = 'solidColor'; + $this.paint.alpha = clr.val('ahex') ? Math.round((clr.val('a') / 255) * 100) : 100; + $this.paint.solidColor = clr.val('hex') ? clr.val('hex') : 'none'; + $this.paint.radialGradient = null; + okClicked(); + }, + null, + function () { cancelClicked(); } + ); + + // JFH !!!! + const tabs = $this.querySelectorAll('.jGraduate_tabs li'); + const onTabsClickHandler = (e) => { + for (const tab of tabs) { + tab.classList.remove('jGraduate_tab_current'); + } + e.target.classList.add('jGraduate_tab_current'); + const innerDivs = $this.querySelectorAll(idref + ' > div'); + [].forEach.call(innerDivs, function (innerDiv) { + innerDiv.style.display = 'none'; + }); + const type = e.target.dataset.type; + gradPicker.style.display = 'block'; + if (type === 'rg' || type === 'lg') { + const tFileds = $this.querySelectorAll('.jGraduate_' + type + '_field'); + [].forEach.call(tFileds, function (tFiled) { + tFiled.style.display = 'block'; + }); + const t1Fileds = $this.querySelectorAll('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field'); + [].forEach.call(t1Fileds, function (tFiled) { + tFiled.style.display = 'none'; + }); + $this.querySelectorAll('#' + id + '_jgraduate_rect')[0] + .setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)'); + curType = type === 'lg' ? 'linearGradient' : 'radialGradient'; + const jOpacInput = $this.querySelector('#' + id + '_jGraduate_OpacInput'); + jOpacInput.value = $this.paint.alpha; + jOpacInput.dispatchEvent(new Event('change')); + const newGrad = $this.querySelectorAll('#' + id + '_' + type + '_jgraduate_grad')[0]; + if (curGradient !== newGrad) { + const curStops = curGradient.querySelectorAll('stop'); + while (newGrad.firstChild) { + newGrad.removeChild(newGrad.firstChild); + } + [].forEach.call(curStops, function (curS) { + newGrad.appendChild(curS); + }); + curGradient = newGrad; + const sm = spreadMethodOpt.getAttribute('value'); + curGradient.setAttribute('spreadMethod', sm); + } + showFocus = type === 'rg' && curGradient.getAttribute('fx') !== null && !(cx === fx && cy === fy); + const jQfocusCoord = $this.querySelectorAll('#' + id + '_jGraduate_focusCoord'); + if (jQfocusCoord[0].style.display === 'none') { + jQfocusCoord[0].style.display = 'block'; + } else { + jQfocusCoord[0].style.display = 'none'; + } + if (showFocus) { + $this.querySelectorAll('#' + id + '_jGraduate_match_ctr')[0].checked = false; + } + } else { + gradPicker.style.display = 'none'; + colPicker.style.display = 'block'; + } + }; + for (const tab of tabs) { + tab.addEventListener('click', onTabsClickHandler); + } + const innerDivs = $this.querySelectorAll(idref + ' > div'); + [].forEach.call(innerDivs, function (innerDiv) { + innerDiv.style.display = 'none'; + }); + for (const tab of tabs) { + tab.classList.remove('jGraduate_tab_current'); + } + let tab; + switch ($this.paint.type) { + case 'linearGradient': + tab = $this.querySelector(idref + ' .jGraduate_tab_lingrad'); + break; + case 'radialGradient': + tab = $this.querySelector(idref + ' .jGraduate_tab_radgrad'); + break; + default: + tab = $this.querySelector(idref + ' .jGraduate_tab_color'); + break; + } + $this.style.display = 'block'; + + // jPicker will try to show after a 0ms timeout, so need to fire this after that + setTimeout(() => { + tab.classList.add('jGraduate_tab_current'); + tab.dispatchEvent(new Event('click')); + }, 10); } diff --git a/src/editor/components/jgraduate/jQuery.jPicker.js b/src/editor/components/jgraduate/jQuery.jPicker.js index 999f13f0..7701848f 100755 --- a/src/editor/components/jgraduate/jQuery.jPicker.js +++ b/src/editor/components/jgraduate/jQuery.jPicker.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len */ +/* eslint-disable unicorn/prefer-modern-dom-apis */ /** * @file jPicker (Adapted from version 1.1.6) * @@ -15,6 +15,13 @@ * John Dyers' website: {@link http://johndyer.name} * Color Picker page: {@link http://johndyer.name/photoshop-like-javascript-color-picker/} */ +/* eslint-disable unicorn/prefer-math-trunc */ +/* eslint-disable no-unsanitized/property */ +/* eslint-disable no-bitwise */ +/* eslint-disable max-len */ +import ColorValuePicker from './ColorValuePicker.js'; +import Slider from './Slider.js'; +import {findPos,mergeDeep} from './Util.js'; /** * @external Math @@ -44,2465 +51,1884 @@ const isNullish = (val) => { * @param {external:jQuery} $ The jQuery object, {@link external:jQuery.fn.$.fn.jPicker}, {@link external:jQuery.fn.$.fn.jPicker.defaults}) * @returns {external:jQuery} */ -const jPicker = function ($) { - /** - * @typedef {PlainObject} module:jPicker.SliderOptions - * @property {external:jQuery|PlainObject} arrow - * @property {string} arrow.image Not in use? - * @property {Float} arrow.width - * @property {Float} arrow.height - * @property {PlainObject} map - * @property {Float} map.width - * @property {Float} map.height - */ +// const jPicker = function ($) { +/** +* @typedef {PlainObject} module:jPicker.SliderOptions +* @property {external:jQuery|PlainObject} arrow +* @property {string} arrow.image Not in use? +* @property {Float} arrow.width +* @property {Float} arrow.height +* @property {PlainObject} map +* @property {Float} map.width +* @property {Float} map.height +*/ +/** +* @typedef {PlainObject} module:jPicker.JPickerInit +* @property {Integer} [a] +* @property {Integer} [b] +* @property {Integer} [g] +* @property {Integer} [h] +* @property {Integer} [r] +* @property {Integer} [s] +* @property {Integer} [v] +* @property {string} [hex] +* @property {string} [ahex] +*/ + +/* eslint-disable jsdoc/require-property */ +/** +* @namespace {PlainObject} jPicker +* @memberof external:jQuery +*/ +export const jPicker = /** @lends external:jQuery.jPicker */ { + /* eslint-enable jsdoc/require-property */ /** - * Encapsulate slider functionality for the ColorMap and ColorBar - - * could be useful to use a jQuery UI draggable for this with certain extensions. - * @memberof module:jPicker + * Array holding references to each active instance of the jPicker control. + * @type {external:jQuery.fn.$.fn.jPicker[]} */ - class Slider { + List: [], + /** + * Color object - we will be able to assign by any color space type or + * retrieve any color space info. + * We want this public so we can optionally assign new color objects to + * initial values using inputs other than a string hex value (also supported) + * Note: JSDoc didn't document when expressed here as an ES6 Class. + * @namespace + * @class + * @memberof external:jQuery.jPicker + * @param {module:jPicker.JPickerInit} init + * @returns {external:jQuery.jPicker.Color} + */ + Color: function (init) { // eslint-disable-line object-shorthand + const that = this; /** - * @param {external:jQuery} bar - * @param {module:jPicker.SliderOptions} options + * + * @param {module:jPicker.Slider} context + * @returns {void} */ - constructor (bar, options) { - const that = this; - /** - * Fire events on the supplied `context` - * @param {module:jPicker.JPickerInit} context - * @returns {void} - */ - function fireChangeEvents (context) { - changeEvents.forEach((changeEvent) => { - changeEvent.call(that, that, context); - }); - } + function fireChangeEvents (context) { + for (let i = 0; i < changeEvents.length; i++) changeEvents[i].call(that, that, context); + } - /** - * Bind the mousedown to the bar not the arrow for quick snapping to the clicked location. - * @param {external:jQuery.Event} e - * @returns {void} - */ - function mouseDown (e) { - const off = bar.offset(); - offset = {l: off.left | 0, t: off.top | 0}; - clearTimeout(timeout); - // using setTimeout for visual updates - once the style is updated the browser will re-render internally allowing the next Javascript to run - timeout = setTimeout(function () { - setValuesFromMousePosition.call(that, e); - }, 0); - // Bind mousemove and mouseup event to the document so it responds when dragged of of the bar - we will unbind these when on mouseup to save processing - $(document).bind('mousemove', mouseMove).bind('mouseup', mouseUp); - e.preventDefault(); // don't try to select anything or drag the image to the desktop - } - /** - * Set the values as the mouse moves. - * @param {external:jQuery.Event} e - * @returns {false} - */ - function mouseMove (e) { - clearTimeout(timeout); - timeout = setTimeout(function () { - setValuesFromMousePosition.call(that, e); - }, 0); - e.stopPropagation(); - e.preventDefault(); - return false; - } - /** - * Unbind the document events - they aren't needed when not dragging. - * @param {external:jQuery.Event} e - * @returns {false} - */ - function mouseUp (e) { - $(document).unbind('mouseup', mouseUp).unbind('mousemove', mouseMove); - e.stopPropagation(); - e.preventDefault(); - return false; - } - - /** - * Calculate mouse position and set value within the current range. - * @param {Event} e - * @returns {void} - */ - function setValuesFromMousePosition (e) { - const barW = bar.w, // local copies for YUI compressor - barH = bar.h; - let locX = e.pageX - offset.l, - locY = e.pageY - offset.t; - // keep the arrow within the bounds of the bar - if (locX < 0) locX = 0; - else if (locX > barW) locX = barW; - if (locY < 0) locY = 0; - else if (locY > barH) locY = barH; - val.call(that, 'xy', { - x: ((locX / barW) * rangeX) + minX, - y: ((locY / barH) * rangeY) + minY - }); - } - /** - * - * @returns {void} - */ - function draw () { - const - barW = bar.w, - barH = bar.h, - arrowW = arrow.w, - arrowH = arrow.h; - let arrowOffsetX = 0, - arrowOffsetY = 0; - setTimeout(function () { - if (rangeX > 0) { // range is greater than zero - // constrain to bounds - if (x === maxX) arrowOffsetX = barW; - else arrowOffsetX = ((x / rangeX) * barW) | 0; - } - if (rangeY > 0) { // range is greater than zero - // constrain to bounds - if (y === maxY) arrowOffsetY = barH; - else arrowOffsetY = ((y / rangeY) * barH) | 0; - } - // if arrow width is greater than bar width, center arrow and prevent horizontal dragging - if (arrowW >= barW) arrowOffsetX = (barW >> 1) - (arrowW >> 1); // number >> 1 - superfast bitwise divide by two and truncate (move bits over one bit discarding lowest) - else arrowOffsetX -= arrowW >> 1; - // if arrow height is greater than bar height, center arrow and prevent vertical dragging - if (arrowH >= barH) arrowOffsetY = (barH >> 1) - (arrowH >> 1); - else arrowOffsetY -= arrowH >> 1; - // set the arrow position based on these offsets - arrow.css({left: arrowOffsetX + 'px', top: arrowOffsetY + 'px'}); - }); - } - - /** - * Get or set a value. - * @param {?("xy"|"x"|"y")} name - * @param {module:math.XYObject} value - * @param {module:jPicker.Slider} context - * @returns {module:math.XYObject|Float|void} - */ - function val (name, value, context) { - const set = value !== undefined; - if (!set) { - if (isNullish(name)) name = 'xy'; - switch (name.toLowerCase()) { - case 'x': return x; - case 'y': return y; - case 'xy': - default: return {x, y}; - } - } - if (!isNullish(context) && context === that) return undefined; - let changed = false; - - let newX, newY; - if (isNullish(name)) name = 'xy'; + /** + * @param {string|"ahex"|"hex"|"all"|""|null|void} name String composed of letters "r", "g", "b", "a", "h", "s", and/or "v" + * @param {module:jPicker.RGBA|module:jPicker.JPickerInit|string} [value] + * @param {external:jQuery.jPicker.Color} context + * @returns {module:jPicker.JPickerInit|string|null|void} + */ + function val (name, value, context) { + // Kind of ugly + const set = Boolean(value); + if (set && value.ahex === '') value.ahex = '00000000'; + if (!set) { + let ret; + if (isNullish(name) || name === '') name = 'all'; + if (isNullish(r)) return null; switch (name.toLowerCase()) { - case 'x': - newX = (value && ((value.x && value.x | 0) || value | 0)) || 0; - break; - case 'y': - newY = (value && ((value.y && value.y | 0) || value | 0)) || 0; - break; - case 'xy': - default: - newX = (value && value.x && value.x | 0) || 0; - newY = (value && value.y && value.y | 0) || 0; - break; + case 'ahex': return ColorMethods.rgbaToHex({r, g, b, a}); + case 'hex': return val('ahex').substring(0, 6); + case 'all': return { + r, g, b, a, h, s, v, + hex: val.call(that, 'hex'), + ahex: val.call(that, 'ahex') + }; + default: { + ret = {}; + const nameLength = name.length; + [...name].forEach((ch) => { + switch (ch) { + case 'r': + if (nameLength === 1) ret = r; + else ret.r = r; + break; + case 'g': + if (nameLength === 1) ret = g; + else ret.g = g; + break; + case 'b': + if (nameLength === 1) ret = b; + else ret.b = b; + break; + case 'a': + if (nameLength === 1) ret = a; + else ret.a = a; + break; + case 'h': + if (nameLength === 1) ret = h; + else ret.h = h; + break; + case 's': + if (nameLength === 1) ret = s; + else ret.s = s; + break; + case 'v': + if (nameLength === 1) ret = v; + else ret.v = v; + break; + } + }); } - if (!isNullish(newX)) { - if (newX < minX) newX = minX; - else if (newX > maxX) newX = maxX; - if (x !== newX) { - x = newX; - changed = true; - } } - if (!isNullish(newY)) { - if (newY < minY) newY = minY; - else if (newY > maxY) newY = maxY; - if (y !== newY) { - y = newY; - changed = true; - } + return typeof ret === 'object' && !Object.keys(ret).length + ? val.call(that, 'all') + : ret; + } + if (!isNullish(context) && context === that) return undefined; + if (isNullish(name)) name = ''; + + let changed = false; + if (isNullish(value)) { + if (!isNullish(r)) { + r = null; + changed = true; + } + if (!isNullish(g)) { + g = null; + changed = true; + } + if (!isNullish(b)) { + b = null; + changed = true; + } + if (!isNullish(a)) { + a = null; + changed = true; + } + if (!isNullish(h)) { + h = null; + changed = true; + } + if (!isNullish(s)) { + s = null; + changed = true; + } + if (!isNullish(v)) { + v = null; + changed = true; } changed && fireChangeEvents.call(that, context || that); return undefined; } - - /** - * @typedef {PlainObject} module:jPicker.MinMaxRangeX - * @property {Float} minX - * @property {Float} maxX - * @property {Float} rangeX - */ - /** - * @typedef {PlainObject} module:jPicker.MinMaxRangeY - * @property {Float} minY - * @property {Float} maxY - * @property {Float} rangeY - */ - /** - * @typedef {module:jPicker.MinMaxRangeY|module:jPicker.MinMaxRangeX} module:jPicker.MinMaxRangeXY - */ - - /** - * - * @param {"minx"|"maxx"|"rangex"|"miny"|"maxy"|"rangey"|"all"} name - * @param {module:jPicker.MinMaxRangeXY} value - * @returns {module:jPicker.MinMaxRangeXY|module:jPicker.MinMaxRangeX|module:jPicker.MinMaxRangeY|void} - */ - function range (name, value) { - const set = value !== undefined; - if (!set) { - if (isNullish(name)) name = 'all'; - switch (name.toLowerCase()) { - case 'minx': return minX; - case 'maxx': return maxX; - case 'rangex': return {minX, maxX, rangeX}; - case 'miny': return minY; - case 'maxy': return maxY; - case 'rangey': return {minY, maxY, rangeY}; - case 'all': - default: return {minX, maxX, rangeX, minY, maxY, rangeY}; - } - } - let // changed = false, - newMinX, - newMaxX, - newMinY, - newMaxY; - if (isNullish(name)) name = 'all'; - switch (name.toLowerCase()) { - case 'minx': - newMinX = (value && ((value.minX && value.minX | 0) || value | 0)) || 0; - break; - case 'maxx': - newMaxX = (value && ((value.maxX && value.maxX | 0) || value | 0)) || 0; - break; - case 'rangex': - newMinX = (value && value.minX && value.minX | 0) || 0; - newMaxX = (value && value.maxX && value.maxX | 0) || 0; - break; - case 'miny': - newMinY = (value && ((value.minY && value.minY | 0) || value | 0)) || 0; - break; - case 'maxy': - newMaxY = (value && ((value.maxY && value.maxY | 0) || value | 0)) || 0; - break; - case 'rangey': - newMinY = (value && value.minY && value.minY | 0) || 0; - newMaxY = (value && value.maxY && value.maxY | 0) || 0; - break; - case 'all': - default: - newMinX = (value && value.minX && value.minX | 0) || 0; - newMaxX = (value && value.maxX && value.maxX | 0) || 0; - newMinY = (value && value.minY && value.minY | 0) || 0; - newMaxY = (value && value.maxY && value.maxY | 0) || 0; - break; - } - - if (!isNullish(newMinX) && minX !== newMinX) { - minX = newMinX; - rangeX = maxX - minX; - } - if (!isNullish(newMaxX) && maxX !== newMaxX) { - maxX = newMaxX; - rangeX = maxX - minX; - } - if (!isNullish(newMinY) && minY !== newMinY) { - minY = newMinY; - rangeY = maxY - minY; - } - if (!isNullish(newMaxY) && maxY !== newMaxY) { - maxY = newMaxY; - rangeY = maxY - minY; - } - return undefined; - } - /** - * @param {GenericCallback} callback - * @returns {void} - */ - function bind (callback) { - if (typeof callback === 'function') changeEvents.push(callback); - } - /** - * @param {GenericCallback} callback - * @returns {void} - */ - function unbind (callback) { - if (typeof callback !== 'function') return; - let i; - while ((i = changeEvents.includes(callback))) changeEvents.splice(i, 1); - } - /** - * - * @returns {void} - */ - function destroy () { - // unbind all possible events and null objects - $(document).unbind('mouseup', mouseUp).unbind('mousemove', mouseMove); - bar.unbind('mousedown', mouseDown); - bar = null; - arrow = null; - changeEvents = null; - } - let offset, - timeout, - x = 0, - y = 0, - minX = 0, - maxX = 100, - rangeX = 100, - minY = 0, - maxY = 100, - rangeY = 100, - arrow = bar.find('img:first'), // the arrow image to drag - changeEvents = []; - - $.extend( - true, - // public properties, methods, and event bindings - these we need - // to access from other controls - that, - { - val, - range, - bind, - unbind, - destroy - } - ); - // initialize this control - arrow.src = options.arrow && options.arrow.image; - arrow.w = (options.arrow && options.arrow.width) || arrow.width(); - arrow.h = (options.arrow && options.arrow.height) || arrow.height(); - bar.w = (options.map && options.map.width) || bar.width(); - bar.h = (options.map && options.map.height) || bar.height(); - // bind mousedown event - bar.bind('mousedown', mouseDown); - bind.call(that, draw); - } - } - - /** - * Controls for all the input elements for the typing in color values. - */ - class ColorValuePicker { - /** - * @param {external:jQuery} picker - * @param {external:jQuery.jPicker.Color} color - * @param {external:jQuery.fn.$.fn.jPicker} bindedHex - * @param {Float} alphaPrecision - */ - constructor (picker, color, bindedHex, alphaPrecision) { - const that = this; // private properties and methods - const inputs = picker.find('td.Text input'); - // input box key down - use arrows to alter color - /** - * - * @param {Event} e - * @returns {Event|false|void} - */ - function keyDown (e) { - if (e.target.value === '' && e.target !== hex.get(0) && ((!isNullish(bindedHex) && e.target !== bindedHex.get(0)) || isNullish(bindedHex))) return undefined; - if (!validateKey(e)) return e; - switch (e.target) { - case red.get(0): - switch (e.keyCode) { - case 38: - red.val(setValueInRange.call(that, (red.val() << 0) + 1, 0, 255)); - color.val('r', red.val(), e.target); - return false; - case 40: - red.val(setValueInRange.call(that, (red.val() << 0) - 1, 0, 255)); - color.val('r', red.val(), e.target); - return false; - } - break; - case green.get(0): - switch (e.keyCode) { - case 38: - green.val(setValueInRange.call(that, (green.val() << 0) + 1, 0, 255)); - color.val('g', green.val(), e.target); - return false; - case 40: - green.val(setValueInRange.call(that, (green.val() << 0) - 1, 0, 255)); - color.val('g', green.val(), e.target); - return false; - } - break; - case blue.get(0): - switch (e.keyCode) { - case 38: - blue.val(setValueInRange.call(that, (blue.val() << 0) + 1, 0, 255)); - color.val('b', blue.val(), e.target); - return false; - case 40: - blue.val(setValueInRange.call(that, (blue.val() << 0) - 1, 0, 255)); - color.val('b', blue.val(), e.target); - return false; - } - break; - case alpha && alpha.get(0): - switch (e.keyCode) { - case 38: - alpha.val(setValueInRange.call(that, Number.parseFloat(alpha.val()) + 1, 0, 100)); - color.val('a', toFixedNumeric((alpha.val() * 255) / 100, alphaPrecision), e.target); - return false; - case 40: - alpha.val(setValueInRange.call(that, Number.parseFloat(alpha.val()) - 1, 0, 100)); - color.val('a', toFixedNumeric((alpha.val() * 255) / 100, alphaPrecision), e.target); - return false; - } - break; - case hue.get(0): - switch (e.keyCode) { - case 38: - hue.val(setValueInRange.call(that, (hue.val() << 0) + 1, 0, 360)); - color.val('h', hue.val(), e.target); - return false; - case 40: - hue.val(setValueInRange.call(that, (hue.val() << 0) - 1, 0, 360)); - color.val('h', hue.val(), e.target); - return false; - } - break; - case saturation.get(0): - switch (e.keyCode) { - case 38: - saturation.val(setValueInRange.call(that, (saturation.val() << 0) + 1, 0, 100)); - color.val('s', saturation.val(), e.target); - return false; - case 40: - saturation.val(setValueInRange.call(that, (saturation.val() << 0) - 1, 0, 100)); - color.val('s', saturation.val(), e.target); - return false; - } - break; - case value.get(0): - switch (e.keyCode) { - case 38: - value.val(setValueInRange.call(that, (value.val() << 0) + 1, 0, 100)); - color.val('v', value.val(), e.target); - return false; - case 40: - value.val(setValueInRange.call(that, (value.val() << 0) - 1, 0, 100)); - color.val('v', value.val(), e.target); - return false; - } - break; - } - return undefined; - } - // input box key up - validate value and set color - /** - * @param {Event} e - * @returns {Event|void} - * @todo Why is this returning an event? - */ - function keyUp (e) { - if (e.target.value === '' && e.target !== hex.get(0) && - ((!isNullish(bindedHex) && e.target !== bindedHex.get(0)) || - isNullish(bindedHex))) return undefined; - if (!validateKey(e)) return e; - switch (e.target) { - case red.get(0): - red.val(setValueInRange.call(that, red.val(), 0, 255)); - color.val('r', red.val(), e.target); - break; - case green.get(0): - green.val(setValueInRange.call(that, green.val(), 0, 255)); - color.val('g', green.val(), e.target); - break; - case blue.get(0): - blue.val(setValueInRange.call(that, blue.val(), 0, 255)); - color.val('b', blue.val(), e.target); - break; - case alpha && alpha.get(0): - alpha.val(setValueInRange.call(that, alpha.val(), 0, 100)); - color.val('a', toFixedNumeric((alpha.val() * 255) / 100, alphaPrecision), e.target); - break; - case hue.get(0): - hue.val(setValueInRange.call(that, hue.val(), 0, 360)); - color.val('h', hue.val(), e.target); - break; - case saturation.get(0): - saturation.val(setValueInRange.call(that, saturation.val(), 0, 100)); - color.val('s', saturation.val(), e.target); - break; - case value.get(0): - value.val(setValueInRange.call(that, value.val(), 0, 100)); - color.val('v', value.val(), e.target); - break; - case hex.get(0): - hex.val(hex.val().replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6)); - bindedHex && bindedHex.val(hex.val()); - color.val('hex', hex.val() !== '' ? hex.val() : null, e.target); - break; - case bindedHex && bindedHex.get(0): - bindedHex.val(bindedHex.val().replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6)); - hex.val(bindedHex.val()); - color.val('hex', bindedHex.val() !== '' ? bindedHex.val() : null, e.target); - break; - case ahex && ahex.get(0): - ahex.val(ahex.val().replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 2)); - color.val('a', !isNullish(ahex.val()) ? Number.parseInt(ahex.val(), 16) : null, e.target); - break; - } - return undefined; - } - // input box blur - reset to original if value empty - /** - * @param {Event} e - * @returns {void} - */ - function blur (e) { - if (!isNullish(color.val())) { - switch (e.target) { - case red.get(0): red.val(color.val('r')); break; - case green.get(0): green.val(color.val('g')); break; - case blue.get(0): blue.val(color.val('b')); break; - case alpha && alpha.get(0): alpha.val(toFixedNumeric((color.val('a') * 100) / 255, alphaPrecision)); break; - case hue.get(0): hue.val(color.val('h')); break; - case saturation.get(0): saturation.val(color.val('s')); break; - case value.get(0): value.val(color.val('v')); break; - case hex.get(0): - case bindedHex && bindedHex.get(0): - hex.val(color.val('hex')); - bindedHex && bindedHex.val(color.val('hex')); - break; - case ahex && ahex.get(0): ahex.val(color.val('ahex').substring(6)); break; - } - } - } - /** - * @param {Event} e - * @returns {boolean} - */ - function validateKey (e) { - switch (e.keyCode) { - case 9: - case 16: - case 29: - case 37: - case 39: - return false; - case 'c'.charCodeAt(): - case 'v'.charCodeAt(): - if (e.ctrlKey) return false; - } - return true; - } - - /** - * Constrain value within range. - * @param {Float|string} value - * @param {Float} min - * @param {Float} max - * @returns {Float|string} Returns a number or numeric string - */ - function setValueInRange (value, min, max) { - if (value === '' || isNaN(value)) return min; - if (value > max) return max; - if (value < min) return min; - return value; - } - /** - * @param {external:jQuery} ui - * @param {Element} context - * @returns {void} - */ - function colorChanged (ui, context) { - const all = ui.val('all'); - if (context !== red.get(0)) red.val(!isNullish(all) ? all.r : ''); - if (context !== green.get(0)) green.val(!isNullish(all) ? all.g : ''); - if (context !== blue.get(0)) blue.val(!isNullish(all) ? all.b : ''); - if (alpha && context !== alpha.get(0)) alpha.val(!isNullish(all) ? toFixedNumeric((all.a * 100) / 255, alphaPrecision) : ''); - if (context !== hue.get(0)) hue.val(!isNullish(all) ? all.h : ''); - if (context !== saturation.get(0)) saturation.val(!isNullish(all) ? all.s : ''); - if (context !== value.get(0)) value.val(!isNullish(all) ? all.v : ''); - if (context !== hex.get(0) && ((bindedHex && context !== bindedHex.get(0)) || !bindedHex)) hex.val(!isNullish(all) ? all.hex : ''); - if (bindedHex && context !== bindedHex.get(0) && context !== hex.get(0)) bindedHex.val(!isNullish(all) ? all.hex : ''); - if (ahex && context !== ahex.get(0)) ahex.val(!isNullish(all) ? all.ahex.substring(6) : ''); - } - /** - * Unbind all events and null objects. - * @returns {void} - */ - function destroy () { - red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).add(hex).add(bindedHex).add(ahex).unbind('keyup', keyUp).unbind('blur', blur); - red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).unbind('keydown', keyDown); - color.unbind(colorChanged); - red = null; - green = null; - blue = null; - alpha = null; - hue = null; - saturation = null; - value = null; - hex = null; - ahex = null; - } - let - red = inputs.eq(3), - green = inputs.eq(4), - blue = inputs.eq(5), - alpha = inputs.length > 7 ? inputs.eq(6) : null, - hue = inputs.eq(0), - saturation = inputs.eq(1), - value = inputs.eq(2), - hex = inputs.eq(inputs.length > 7 ? 7 : 6), - ahex = inputs.length > 7 ? inputs.eq(8) : null; - $.extend(true, that, { - // public properties and methods - destroy - }); - red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).add(hex).add(bindedHex).add(ahex).bind('keyup', keyUp).bind('blur', blur); - red.add(green).add(blue).add(alpha).add(hue).add(saturation).add(value).bind('keydown', keyDown); - color.bind(colorChanged); - } - } - - /** - * @typedef {PlainObject} module:jPicker.JPickerInit - * @property {Integer} [a] - * @property {Integer} [b] - * @property {Integer} [g] - * @property {Integer} [h] - * @property {Integer} [r] - * @property {Integer} [s] - * @property {Integer} [v] - * @property {string} [hex] - * @property {string} [ahex] - */ - - /** - * @namespace {PlainObject} jPicker - * @memberof external:jQuery - */ - $.jPicker = /** @lends external:jQuery.jPicker */ { - /** - * Array holding references to each active instance of the jPicker control. - * @type {external:jQuery.fn.$.fn.jPicker[]} - */ - List: [], - /** - * Color object - we will be able to assign by any color space type or - * retrieve any color space info. - * We want this public so we can optionally assign new color objects to - * initial values using inputs other than a string hex value (also supported) - * Note: JSDoc didn't document when expressed here as an ES6 Class. - * @namespace - * @class - * @memberof external:jQuery.jPicker - * @param {module:jPicker.JPickerInit} init - * @returns {external:jQuery.jPicker.Color} - */ - Color: function (init) { // eslint-disable-line object-shorthand - const that = this; - /** - * - * @param {module:jPicker.Slider} context - * @returns {void} - */ - function fireChangeEvents (context) { - for (let i = 0; i < changeEvents.length; i++) changeEvents[i].call(that, that, context); - } - - /** - * @param {string|"ahex"|"hex"|"all"|""|null|void} name String composed of letters "r", "g", "b", "a", "h", "s", and/or "v" - * @param {module:jPicker.RGBA|module:jPicker.JPickerInit|string} [value] - * @param {external:jQuery.jPicker.Color} context - * @returns {module:jPicker.JPickerInit|string|null|void} - */ - function val (name, value, context) { - // Kind of ugly - const set = Boolean(value); - if (set && value.ahex === '') value.ahex = '00000000'; - if (!set) { - let ret; - if (isNullish(name) || name === '') name = 'all'; - if (isNullish(r)) return null; - switch (name.toLowerCase()) { - case 'ahex': return ColorMethods.rgbaToHex({r, g, b, a}); - case 'hex': return val('ahex').substring(0, 6); - case 'all': return { - r, g, b, a, h, s, v, - hex: val.call(that, 'hex'), - ahex: val.call(that, 'ahex') - }; - default: { - ret = {}; - const nameLength = name.length; - [...name].forEach((ch) => { - switch (ch) { - case 'r': - if (nameLength === 1) ret = r; - else ret.r = r; - break; - case 'g': - if (nameLength === 1) ret = g; - else ret.g = g; - break; - case 'b': - if (nameLength === 1) ret = b; - else ret.b = b; - break; - case 'a': - if (nameLength === 1) ret = a; - else ret.a = a; - break; - case 'h': - if (nameLength === 1) ret = h; - else ret.h = h; - break; - case 's': - if (nameLength === 1) ret = s; - else ret.s = s; - break; - case 'v': - if (nameLength === 1) ret = v; - else ret.v = v; - break; - } - }); - } - } - return typeof ret === 'object' && !Object.keys(ret).length - ? val.call(that, 'all') - : ret; - } - if (!isNullish(context) && context === that) return undefined; - if (isNullish(name)) name = ''; - - let changed = false; - if (isNullish(value)) { - if (!isNullish(r)) { - r = null; - changed = true; - } - if (!isNullish(g)) { - g = null; - changed = true; - } - if (!isNullish(b)) { - b = null; - changed = true; - } - if (!isNullish(a)) { - a = null; - changed = true; - } - if (!isNullish(h)) { - h = null; - changed = true; - } - if (!isNullish(s)) { - s = null; - changed = true; - } - if (!isNullish(v)) { - v = null; - changed = true; - } - changed && fireChangeEvents.call(that, context || that); + switch (name.toLowerCase()) { + case 'ahex': + case 'hex': { + const ret = ColorMethods.hexToRgba((value && (value.ahex || value.hex)) || value || 'none'); + val.call(that, 'rgba', { + r: ret.r, + g: ret.g, + b: ret.b, + a: name === 'ahex' + ? ret.a + : !isNullish(a) + ? a + : 255 + }, context); + break; + } default: { + if (value && (!isNullish(value.ahex) || !isNullish(value.hex))) { + val.call(that, 'ahex', value.ahex || value.hex || '00000000', context); return undefined; } - switch (name.toLowerCase()) { - case 'ahex': - case 'hex': { - const ret = ColorMethods.hexToRgba((value && (value.ahex || value.hex)) || value || 'none'); - val.call(that, 'rgba', { - r: ret.r, - g: ret.g, - b: ret.b, - a: name === 'ahex' - ? ret.a - : !isNullish(a) - ? a - : 255 - }, context); - break; - } default: { - if (value && (!isNullish(value.ahex) || !isNullish(value.hex))) { - val.call(that, 'ahex', value.ahex || value.hex || '00000000', context); - return undefined; - } - const newV = {}; - let rgb = false, hsv = false; - if (value.r !== undefined && !name.includes('r')) name += 'r'; - if (value.g !== undefined && !name.includes('g')) name += 'g'; - if (value.b !== undefined && !name.includes('b')) name += 'b'; - if (value.a !== undefined && !name.includes('a')) name += 'a'; - if (value.h !== undefined && !name.includes('h')) name += 'h'; - if (value.s !== undefined && !name.includes('s')) name += 's'; - if (value.v !== undefined && !name.includes('v')) name += 'v'; - [...name].forEach((ch) => { - switch (ch) { - case 'r': - if (hsv) return; - rgb = true; - newV.r = (value.r && value.r | 0) || (value | 0) || 0; - if (newV.r < 0) newV.r = 0; - else if (newV.r > 255) newV.r = 255; - if (r !== newV.r) { - ({r} = newV); - changed = true; - } - break; - case 'g': - if (hsv) return; - rgb = true; - newV.g = (value && value.g && value.g | 0) || (value && value | 0) || 0; - if (newV.g < 0) newV.g = 0; - else if (newV.g > 255) newV.g = 255; - if (g !== newV.g) { - ({g} = newV); - changed = true; - } - break; - case 'b': - if (hsv) return; - rgb = true; - newV.b = (value && value.b && value.b | 0) || (value && value | 0) || 0; - if (newV.b < 0) newV.b = 0; - else if (newV.b > 255) newV.b = 255; - if (b !== newV.b) { - ({b} = newV); - changed = true; - } - break; - case 'a': - newV.a = value && !isNullish(value.a) ? value.a | 0 : value | 0; - if (newV.a < 0) newV.a = 0; - else if (newV.a > 255) newV.a = 255; - if (a !== newV.a) { - ({a} = newV); - changed = true; - } - break; - case 'h': - if (rgb) return; - hsv = true; - newV.h = (value && value.h && value.h | 0) || (value && value | 0) || 0; - if (newV.h < 0) newV.h = 0; - else if (newV.h > 360) newV.h = 360; - if (h !== newV.h) { - ({h} = newV); - changed = true; - } - break; - case 's': - if (rgb) return; - hsv = true; - newV.s = !isNullish(value.s) ? value.s | 0 : value | 0; - if (newV.s < 0) newV.s = 0; - else if (newV.s > 100) newV.s = 100; - if (s !== newV.s) { - ({s} = newV); - changed = true; - } - break; - case 'v': - if (rgb) return; - hsv = true; - newV.v = !isNullish(value.v) ? value.v | 0 : value | 0; - if (newV.v < 0) newV.v = 0; - else if (newV.v > 100) newV.v = 100; - if (v !== newV.v) { - ({v} = newV); - changed = true; - } - break; + const newV = {}; + let rgb = false, hsv = false; + if (value.r !== undefined && !name.includes('r')) name += 'r'; + if (value.g !== undefined && !name.includes('g')) name += 'g'; + if (value.b !== undefined && !name.includes('b')) name += 'b'; + if (value.a !== undefined && !name.includes('a')) name += 'a'; + if (value.h !== undefined && !name.includes('h')) name += 'h'; + if (value.s !== undefined && !name.includes('s')) name += 's'; + if (value.v !== undefined && !name.includes('v')) name += 'v'; + [...name].forEach((ch) => { + switch (ch) { + case 'r': + if (hsv) return; + rgb = true; + newV.r = (value.r && value.r | 0) || (value | 0) || 0; + if (newV.r < 0) newV.r = 0; + else if (newV.r > 255) newV.r = 255; + if (r !== newV.r) { + ({r} = newV); + changed = true; } - }); - if (changed) { - if (rgb) { - r = r || 0; - g = g || 0; - b = b || 0; - const ret = ColorMethods.rgbToHsv({r, g, b}); - ({h, s, v} = ret); - } else if (hsv) { - h = h || 0; - s = !isNullish(s) ? s : 100; - v = !isNullish(v) ? v : 100; - const ret = ColorMethods.hsvToRgb({h, s, v}); - ({r, g, b} = ret); - } - a = !isNullish(a) ? a : 255; - fireChangeEvents.call(that, context || that); - } - break; - } - } - return undefined; - } - /** - * @param {GenericCallback} callback - * @returns {void} - */ - function bind (callback) { - if (typeof callback === 'function') changeEvents.push(callback); - } - /** - * @param {GenericCallback} callback - * @returns {void} - */ - function unbind (callback) { - if (typeof callback !== 'function') return; - let i; - while ((i = changeEvents.includes(callback))) { - changeEvents.splice(i, 1); - } - } - /** - * Unset `changeEvents` - * @returns {void} - */ - function destroy () { - changeEvents = null; - } - let r, g, b, a, h, s, v, changeEvents = []; - - $.extend(true, that, { - // public properties and methods - val, - bind, - unbind, - destroy - }); - if (init) { - if (!isNullish(init.ahex)) { - val('ahex', init); - } else if (!isNullish(init.hex)) { - val( - (!isNullish(init.a) ? 'a' : '') + 'hex', - !isNullish(init.a) - ? {ahex: init.hex + ColorMethods.intToHex(init.a)} - : init - ); - } else if (!isNullish(init.r) && !isNullish(init.g) && !isNullish(init.b)) { - val('rgb' + (!isNullish(init.a) ? 'a' : ''), init); - } else if (!isNullish(init.h) && !isNullish(init.s) && !isNullish(init.v)) { - val('hsv' + (!isNullish(init.a) ? 'a' : ''), init); - } - } - }, - /** - * Color conversion methods - make public to give use to external scripts. - * @namespace - */ - ColorMethods: { - /** - * @typedef {PlainObject} module:jPicker.RGBA - * @property {Integer} r - * @property {Integer} g - * @property {Integer} b - * @property {Integer} a - */ - /** - * @typedef {PlainObject} module:jPicker.RGB - * @property {Integer} r - * @property {Integer} g - * @property {Integer} b - */ - /** - * @param {string} hex - * @returns {module:jPicker.RGBA} - */ - hexToRgba (hex) { - if (hex === '' || hex === 'none') return {r: null, g: null, b: null, a: null}; - hex = this.validateHex(hex); - let r = '00', g = '00', b = '00', a = '255'; - if (hex.length === 6) hex += 'ff'; - if (hex.length > 6) { - r = hex.substring(0, 2); - g = hex.substring(2, 4); - b = hex.substring(4, 6); - a = hex.substring(6, hex.length); - } else { - if (hex.length > 4) { - r = hex.substring(4, hex.length); - hex = hex.substring(0, 4); - } - if (hex.length > 2) { - g = hex.substring(2, hex.length); - hex = hex.substring(0, 2); - } - if (hex.length > 0) b = hex.substring(0, hex.length); - } - return { - r: this.hexToInt(r), g: this.hexToInt(g), b: this.hexToInt(b), a: this.hexToInt(a) - }; - }, - /** - * @param {string} hex - * @returns {string} - */ - validateHex (hex) { - // if (typeof hex === 'object') return ''; - hex = hex.toLowerCase().replace(/[^a-f\d]/g, ''); - if (hex.length > 8) hex = hex.substring(0, 8); - return hex; - }, - /** - * @param {module:jPicker.RGBA} rgba - * @returns {string} - */ - rgbaToHex (rgba) { - return this.intToHex(rgba.r) + this.intToHex(rgba.g) + this.intToHex(rgba.b) + this.intToHex(rgba.a); - }, - /** - * @param {Integer} dec - * @returns {string} - */ - intToHex (dec) { - let result = (dec | 0).toString(16); - if (result.length === 1) result = ('0' + result); - return result.toLowerCase(); - }, - /** - * @param {string} hex - * @returns {Integer} - */ - hexToInt (hex) { - return Number.parseInt(hex, 16); - }, - /** - * @typedef {PlainObject} module:jPicker.HSV - * @property {Integer} h - * @property {Integer} s - * @property {Integer} v - */ - /** - * @param {module:jPicker.RGB} rgb - * @returns {module:jPicker.HSV} - */ - rgbToHsv (rgb) { - const r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255, hsv = {h: 0, s: 0, v: 0}; - let min = 0, max = 0; - if (r >= g && r >= b) { - max = r; - min = g > b ? b : g; - } else if (g >= b && g >= r) { - max = g; - min = r > b ? b : r; - } else { - max = b; - min = g > r ? r : g; - } - hsv.v = max; - hsv.s = max ? (max - min) / max : 0; - let delta; - if (!hsv.s) hsv.h = 0; - else { - delta = max - min; - if (r === max) hsv.h = (g - b) / delta; - else if (g === max) hsv.h = 2 + (b - r) / delta; - else hsv.h = 4 + (r - g) / delta; - hsv.h = Number.parseInt(hsv.h * 60); - if (hsv.h < 0) hsv.h += 360; - } - hsv.s = (hsv.s * 100) | 0; - hsv.v = (hsv.v * 100) | 0; - return hsv; - }, - /** - * @param {module:jPicker.HSV} hsv - * @returns {module:jPicker.RGB} - */ - hsvToRgb (hsv) { - const rgb = {r: 0, g: 0, b: 0, a: 100}; - let {h, s, v} = hsv; - if (s === 0) { - if (v === 0) rgb.r = rgb.g = rgb.b = 0; - else rgb.r = rgb.g = rgb.b = (v * 255 / 100) | 0; - } else { - if (h === 360) h = 0; - h /= 60; - s /= 100; - v /= 100; - const i = h | 0, - f = h - i, - p = v * (1 - s), - q = v * (1 - (s * f)), - t = v * (1 - (s * (1 - f))); - switch (i) { - case 0: - rgb.r = v; - rgb.g = t; - rgb.b = p; - break; - case 1: - rgb.r = q; - rgb.g = v; - rgb.b = p; - break; - case 2: - rgb.r = p; - rgb.g = v; - rgb.b = t; - break; - case 3: - rgb.r = p; - rgb.g = q; - rgb.b = v; - break; - case 4: - rgb.r = t; - rgb.g = p; - rgb.b = v; - break; - case 5: - rgb.r = v; - rgb.g = p; - rgb.b = q; - break; - } - rgb.r = (rgb.r * 255) | 0; - rgb.g = (rgb.g * 255) | 0; - rgb.b = (rgb.b * 255) | 0; - } - return rgb; - } - } - }; - const {Color, List, ColorMethods} = $.jPicker; // local copies for YUI compressor - /** - * @function external:jQuery.fn.jPicker - * @see {@link external:jQuery.fn.$.fn.jPicker} - */ - - /** - * Will be bound to active {@link jQuery.jPicker.Color}. - * @callback module:jPicker.LiveCallback - * @param {external:jQuery} ui - * @param {Element} context - * @returns {void} - */ - /** - * @callback module:jPicker.CommitCallback - * @param {external:jQuery.jPicker.Color} activeColor - * @param {external:jQuery} okButton - * @returns {void} Return value not used. - */ - /** - * @callback module:jPicker.CancelCallback - * @param {external:jQuery.jPicker.Color} activeColor - * @param {external:jQuery} cancelButton - * @returns {void} Return value not used. - */ - /** - * While it would seem this should specify the name `jPicker` for JSDoc, that doesn't - * get us treated as a function as well as a namespace (even with `@function name`), - * so we use an approach to add a redundant `$.fn.` in the name. - * @namespace - * @memberof external:jQuery.fn - * @param {external:jQuery.fn.jPickerOptions} options - * @param {module:jPicker.CommitCallback} [commitCallback] - * @param {module:jPicker.LiveCallback} [liveCallback] - * @param {module:jPicker.CancelCallback} [cancelCallback] - * @returns {external:jQuery} - */ - $.fn.jPicker = function (options, commitCallback, liveCallback, cancelCallback) { - return this.each(function () { - const that = this, - settings = $.extend(true, {}, $.fn.jPicker.defaults, options); // local copies for YUI compressor - if ($(that).get(0).nodeName.toLowerCase() === 'input') { // Add color picker icon if binding to an input element and bind the events to the input - $.extend(true, settings, { - window: { - bindToInput: true, - expandable: true, - input: $(that) - } - }); - if ($(that).val() === '') { - settings.color.active = new Color({hex: null}); - settings.color.current = new Color({hex: null}); - } else if (ColorMethods.validateHex($(that).val())) { - settings.color.active = new Color({hex: $(that).val(), a: settings.color.active.val('a')}); - settings.color.current = new Color({hex: $(that).val(), a: settings.color.active.val('a')}); - } - } - if (settings.window.expandable) { - $(that).after('    '); - } else { - settings.window.liveUpdate = false; // Basic control binding for inline use - You will need to override the liveCallback or commitCallback function to retrieve results - } - const isLessThanIE7 = Number.parseFloat(navigator.appVersion.split('MSIE')[1]) < 7 && document.body.filters; // needed to run the AlphaImageLoader function for IE6 - // set color mode and update visuals for the new color mode - /** - * - * @param {"h"|"s"|"v"|"r"|"g"|"b"|"a"} colorMode - * @throws {Error} Invalid mode - * @returns {void} - */ - function setColorMode (colorMode) { - const {active} = color, // local copies for YUI compressor - // {clientPath} = images, - hex = active.val('hex'); - let rgbMap, rgbBar; - settings.color.mode = colorMode; - switch (colorMode) { - case 'h': - setTimeout(function () { - setBG.call(that, colorMapDiv, 'transparent'); - setImgLoc.call(that, colorMapL1, 0); - setAlpha.call(that, colorMapL1, 100); - setImgLoc.call(that, colorMapL2, 260); - setAlpha.call(that, colorMapL2, 100); - setBG.call(that, colorBarDiv, 'transparent'); - setImgLoc.call(that, colorBarL1, 0); - setAlpha.call(that, colorBarL1, 100); - setImgLoc.call(that, colorBarL2, 260); - setAlpha.call(that, colorBarL2, 100); - setImgLoc.call(that, colorBarL3, 260); - setAlpha.call(that, colorBarL3, 100); - setImgLoc.call(that, colorBarL4, 260); - setAlpha.call(that, colorBarL4, 100); - setImgLoc.call(that, colorBarL6, 260); - setAlpha.call(that, colorBarL6, 100); - }, 0); - colorMap.range('all', {minX: 0, maxX: 100, minY: 0, maxY: 100}); - colorBar.range('rangeY', {minY: 0, maxY: 360}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('s'), y: 100 - active.val('v')}, colorMap); - colorBar.val('y', 360 - active.val('h'), colorBar); - break; - case 's': - setTimeout(function () { - setBG.call(that, colorMapDiv, 'transparent'); - setImgLoc.call(that, colorMapL1, -260); - setImgLoc.call(that, colorMapL2, -520); - setImgLoc.call(that, colorBarL1, -260); - setImgLoc.call(that, colorBarL2, -520); - setImgLoc.call(that, colorBarL6, 260); - setAlpha.call(that, colorBarL6, 100); - }, 0); - colorMap.range('all', {minX: 0, maxX: 360, minY: 0, maxY: 100}); - colorBar.range('rangeY', {minY: 0, maxY: 100}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('h'), y: 100 - active.val('v')}, colorMap); - colorBar.val('y', 100 - active.val('s'), colorBar); - break; - case 'v': - setTimeout(function () { - setBG.call(that, colorMapDiv, '000000'); - setImgLoc.call(that, colorMapL1, -780); - setImgLoc.call(that, colorMapL2, 260); - setBG.call(that, colorBarDiv, hex); - setImgLoc.call(that, colorBarL1, -520); - setImgLoc.call(that, colorBarL2, 260); - setAlpha.call(that, colorBarL2, 100); - setImgLoc.call(that, colorBarL6, 260); - setAlpha.call(that, colorBarL6, 100); - }, 0); - colorMap.range('all', {minX: 0, maxX: 360, minY: 0, maxY: 100}); - colorBar.range('rangeY', {minY: 0, maxY: 100}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('h'), y: 100 - active.val('s')}, colorMap); - colorBar.val('y', 100 - active.val('v'), colorBar); - break; - case 'r': - rgbMap = -1040; - rgbBar = -780; - colorMap.range('all', {minX: 0, maxX: 255, minY: 0, maxY: 255}); - colorBar.range('rangeY', {minY: 0, maxY: 255}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('b'), y: 255 - active.val('g')}, colorMap); - colorBar.val('y', 255 - active.val('r'), colorBar); - break; - case 'g': - rgbMap = -1560; - rgbBar = -1820; - colorMap.range('all', {minX: 0, maxX: 255, minY: 0, maxY: 255}); - colorBar.range('rangeY', {minY: 0, maxY: 255}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('b'), y: 255 - active.val('r')}, colorMap); - colorBar.val('y', 255 - active.val('g'), colorBar); - break; - case 'b': - rgbMap = -2080; - rgbBar = -2860; - colorMap.range('all', {minX: 0, maxX: 255, minY: 0, maxY: 255}); - colorBar.range('rangeY', {minY: 0, maxY: 255}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('r'), y: 255 - active.val('g')}, colorMap); - colorBar.val('y', 255 - active.val('b'), colorBar); - break; - case 'a': - setTimeout(function () { - setBG.call(that, colorMapDiv, 'transparent'); - setImgLoc.call(that, colorMapL1, -260); - setImgLoc.call(that, colorMapL2, -520); - setImgLoc.call(that, colorBarL1, 260); - setImgLoc.call(that, colorBarL2, 260); - setAlpha.call(that, colorBarL2, 100); - setImgLoc.call(that, colorBarL6, 0); - setAlpha.call(that, colorBarL6, 100); - }, 0); - colorMap.range('all', {minX: 0, maxX: 360, minY: 0, maxY: 100}); - colorBar.range('rangeY', {minY: 0, maxY: 255}); - if (isNullish(active.val('ahex'))) break; - colorMap.val('xy', {x: active.val('h'), y: 100 - active.val('v')}, colorMap); - colorBar.val('y', 255 - active.val('a'), colorBar); - break; - default: - throw new Error('Invalid Mode'); - } - switch (colorMode) { - case 'h': - break; - case 's': - case 'v': - case 'a': - setTimeout(function () { - setAlpha.call(that, colorMapL1, 100); - setAlpha.call(that, colorBarL1, 100); - setImgLoc.call(that, colorBarL3, 260); - setAlpha.call(that, colorBarL3, 100); - setImgLoc.call(that, colorBarL4, 260); - setAlpha.call(that, colorBarL4, 100); - }, 0); - break; - case 'r': - case 'g': - case 'b': - setTimeout(function () { - setBG.call(that, colorMapDiv, 'transparent'); - setBG.call(that, colorBarDiv, 'transparent'); - setAlpha.call(that, colorBarL1, 100); - setAlpha.call(that, colorMapL1, 100); - setImgLoc.call(that, colorMapL1, rgbMap); - setImgLoc.call(that, colorMapL2, rgbMap - 260); - setImgLoc.call(that, colorBarL1, rgbBar - 780); - setImgLoc.call(that, colorBarL2, rgbBar - 520); - setImgLoc.call(that, colorBarL3, rgbBar); - setImgLoc.call(that, colorBarL4, rgbBar - 260); - setImgLoc.call(that, colorBarL6, 260); - setAlpha.call(that, colorBarL6, 100); - }, 0); - break; - } - if (isNullish(active.val('ahex'))) return; - activeColorChanged.call(that, active); - } - /** - * Update color when user changes text values. - * @param {external:jQuery} ui - * @param {?module:jPicker.Slider} context - * @returns {void} - */ - function activeColorChanged (ui, context) { - if (isNullish(context) || (context !== colorBar && context !== colorMap)) positionMapAndBarArrows.call(that, ui, context); - setTimeout(function () { - updatePreview.call(that, ui); - updateMapVisuals.call(that, ui); - updateBarVisuals.call(that, ui); - }, 0); - } - - /** - * User has dragged the ColorMap pointer. - * @param {external:jQuery} ui - * @param {?module:jPicker.Slider} context - * @returns {void} - */ - function mapValueChanged (ui, context) { - const {active} = color; - if (context !== colorMap && isNullish(active.val())) return; - const xy = ui.val('all'); - switch (settings.color.mode) { - case 'h': - active.val('sv', {s: xy.x, v: 100 - xy.y}, context); - break; - case 's': - case 'a': - active.val('hv', {h: xy.x, v: 100 - xy.y}, context); - break; - case 'v': - active.val('hs', {h: xy.x, s: 100 - xy.y}, context); - break; - case 'r': - active.val('gb', {g: 255 - xy.y, b: xy.x}, context); - break; - case 'g': - active.val('rb', {r: 255 - xy.y, b: xy.x}, context); - break; - case 'b': - active.val('rg', {r: xy.x, g: 255 - xy.y}, context); - break; - } - } - - /** - * User has dragged the ColorBar slider. - * @param {external:jQuery} ui - * @param {?module:jPicker.Slider} context - * @returns {void} - */ - function colorBarValueChanged (ui, context) { - const {active} = color; - if (context !== colorBar && isNullish(active.val())) return; - switch (settings.color.mode) { - case 'h': - active.val('h', {h: 360 - ui.val('y')}, context); - break; - case 's': - active.val('s', {s: 100 - ui.val('y')}, context); - break; - case 'v': - active.val('v', {v: 100 - ui.val('y')}, context); - break; - case 'r': - active.val('r', {r: 255 - ui.val('y')}, context); - break; - case 'g': - active.val('g', {g: 255 - ui.val('y')}, context); - break; - case 'b': - active.val('b', {b: 255 - ui.val('y')}, context); - break; - case 'a': - active.val('a', 255 - ui.val('y'), context); - break; - } - } - - /** - * Position map and bar arrows to match current color. - * @param {external:jQuery} ui - * @param {?module:jPicker.Slider} context - * @returns {void} - */ - function positionMapAndBarArrows (ui, context) { - if (context !== colorMap) { - switch (settings.color.mode) { - case 'h': { - const sv = ui.val('sv'); - colorMap.val('xy', {x: !isNullish(sv) ? sv.s : 100, y: 100 - (!isNullish(sv) ? sv.v : 100)}, context); - break; - } case 's': - // Fall through - case 'a': { - const hv = ui.val('hv'); - colorMap.val('xy', {x: (hv && hv.h) || 0, y: 100 - (!isNullish(hv) ? hv.v : 100)}, context); - break; - } case 'v': { - const hs = ui.val('hs'); - colorMap.val('xy', {x: (hs && hs.h) || 0, y: 100 - (!isNullish(hs) ? hs.s : 100)}, context); - break; - } case 'r': { - const bg = ui.val('bg'); - colorMap.val('xy', {x: (bg && bg.b) || 0, y: 255 - ((bg && bg.g) || 0)}, context); - break; - } case 'g': { - const br = ui.val('br'); - colorMap.val('xy', {x: (br && br.b) || 0, y: 255 - ((br && br.r) || 0)}, context); - break; - } case 'b': { - const rg = ui.val('rg'); - colorMap.val('xy', {x: (rg && rg.r) || 0, y: 255 - ((rg && rg.g) || 0)}, context); - break; - } - } - } - if (context !== colorBar) { - switch (settings.color.mode) { - case 'h': - colorBar.val('y', 360 - (ui.val('h') || 0), context); - break; - case 's': { - const s = ui.val('s'); - colorBar.val('y', 100 - (!isNullish(s) ? s : 100), context); - break; - } case 'v': { - const v = ui.val('v'); - colorBar.val('y', 100 - (!isNullish(v) ? v : 100), context); - break; - } case 'r': - colorBar.val('y', 255 - (ui.val('r') || 0), context); break; case 'g': - colorBar.val('y', 255 - (ui.val('g') || 0), context); + if (hsv) return; + rgb = true; + newV.g = (value && value.g && value.g | 0) || (value && value | 0) || 0; + if (newV.g < 0) newV.g = 0; + else if (newV.g > 255) newV.g = 255; + if (g !== newV.g) { + ({g} = newV); + changed = true; + } break; case 'b': - colorBar.val('y', 255 - (ui.val('b') || 0), context); + if (hsv) return; + rgb = true; + newV.b = (value && value.b && value.b | 0) || (value && value | 0) || 0; + if (newV.b < 0) newV.b = 0; + else if (newV.b > 255) newV.b = 255; + if (b !== newV.b) { + ({b} = newV); + changed = true; + } break; - case 'a': { - const a = ui.val('a'); - colorBar.val('y', 255 - (!isNullish(a) ? a : 255), context); + case 'a': + newV.a = value && !isNullish(value.a) ? value.a | 0 : value | 0; + if (newV.a < 0) newV.a = 0; + else if (newV.a > 255) newV.a = 255; + if (a !== newV.a) { + ({a} = newV); + changed = true; + } + break; + case 'h': + if (rgb) return; + hsv = true; + newV.h = (value && value.h && value.h | 0) || (value && value | 0) || 0; + if (newV.h < 0) newV.h = 0; + else if (newV.h > 360) newV.h = 360; + if (h !== newV.h) { + ({h} = newV); + changed = true; + } + break; + case 's': + if (rgb) return; + hsv = true; + newV.s = !isNullish(value.s) ? value.s | 0 : value | 0; + if (newV.s < 0) newV.s = 0; + else if (newV.s > 100) newV.s = 100; + if (s !== newV.s) { + ({s} = newV); + changed = true; + } + break; + case 'v': + if (rgb) return; + hsv = true; + newV.v = !isNullish(value.v) ? value.v | 0 : value | 0; + if (newV.v < 0) newV.v = 0; + else if (newV.v > 100) newV.v = 100; + if (v !== newV.v) { + ({v} = newV); + changed = true; + } break; } - } - } - } - /** - * @param {external:jQuery} ui - * @returns {void} - */ - function updatePreview (ui) { - try { - const all = ui.val('all'); - activePreview.css({backgroundColor: (all && '#' + all.hex) || 'transparent'}); - setAlpha.call(that, activePreview, (all && toFixedNumeric((all.a * 100) / 255, 4)) || 0); - } catch (e) {/* empty fn */} - } - /** - * @param {external:jQuery} ui - * @returns {void} - */ - function updateMapVisuals (ui) { - switch (settings.color.mode) { - case 'h': - setBG.call(that, colorMapDiv, new Color({h: ui.val('h') || 0, s: 100, v: 100}).val('hex')); - break; - case 's': - case 'a': { - const s = ui.val('s'); - setAlpha.call(that, colorMapL2, 100 - (!isNullish(s) ? s : 100)); - break; - } case 'v': { - const v = ui.val('v'); - setAlpha.call(that, colorMapL1, !isNullish(v) ? v : 100); - break; - } case 'r': - setAlpha.call(that, colorMapL2, toFixedNumeric((ui.val('r') || 0) / 255 * 100, 4)); - break; - case 'g': - setAlpha.call(that, colorMapL2, toFixedNumeric((ui.val('g') || 0) / 255 * 100, 4)); - break; - case 'b': - setAlpha.call(that, colorMapL2, toFixedNumeric((ui.val('b') || 0) / 255 * 100)); - break; - } - const a = ui.val('a'); - setAlpha.call(that, colorMapL3, toFixedNumeric(((255 - (a || 0)) * 100) / 255, 4)); - } - /** - * @param {external:jQuery} ui - * @returns {void} - */ - function updateBarVisuals (ui) { - switch (settings.color.mode) { - case 'h': { - const a = ui.val('a'); - setAlpha.call(that, colorBarL5, toFixedNumeric(((255 - (a || 0)) * 100) / 255, 4)); - break; - } case 's': { - const hva = ui.val('hva'), - saturatedColor = new Color({h: (hva && hva.h) || 0, s: 100, v: !isNullish(hva) ? hva.v : 100}); - setBG.call(that, colorBarDiv, saturatedColor.val('hex')); - setAlpha.call(that, colorBarL2, 100 - (!isNullish(hva) ? hva.v : 100)); - setAlpha.call(that, colorBarL5, toFixedNumeric(((255 - ((hva && hva.a) || 0)) * 100) / 255, 4)); - break; - } case 'v': { - const hsa = ui.val('hsa'), - valueColor = new Color({h: (hsa && hsa.h) || 0, s: !isNullish(hsa) ? hsa.s : 100, v: 100}); - setBG.call(that, colorBarDiv, valueColor.val('hex')); - setAlpha.call(that, colorBarL5, toFixedNumeric(((255 - ((hsa && hsa.a) || 0)) * 100) / 255, 4)); - break; - } case 'r': - case 'g': - case 'b': { - const rgba = ui.val('rgba'); - let hValue = 0, vValue = 0; - if (settings.color.mode === 'r') { - hValue = (rgba && rgba.b) || 0; - vValue = (rgba && rgba.g) || 0; - } else if (settings.color.mode === 'g') { - hValue = (rgba && rgba.b) || 0; - vValue = (rgba && rgba.r) || 0; - } else if (settings.color.mode === 'b') { - hValue = (rgba && rgba.r) || 0; - vValue = (rgba && rgba.g) || 0; - } - const middle = vValue > hValue ? hValue : vValue; - setAlpha.call(that, colorBarL2, hValue > vValue ? toFixedNumeric(((hValue - vValue) / (255 - vValue)) * 100, 4) : 0); - setAlpha.call(that, colorBarL3, vValue > hValue ? toFixedNumeric(((vValue - hValue) / (255 - hValue)) * 100, 4) : 0); - setAlpha.call(that, colorBarL4, toFixedNumeric((middle / 255) * 100, 4)); - setAlpha.call(that, colorBarL5, toFixedNumeric(((255 - ((rgba && rgba.a) || 0)) * 100) / 255, 4)); - break; - } case 'a': { - const a = ui.val('a'); - setBG.call(that, colorBarDiv, ui.val('hex') || '000000'); - setAlpha.call(that, colorBarL5, !isNullish(a) ? 0 : 100); - setAlpha.call(that, colorBarL6, !isNullish(a) ? 100 : 0); - break; - } - } - } - /** - * @param {external:jQuery} el - * @param {string} [c="transparent"] - * @returns {void} - */ - function setBG (el, c) { - el.css({backgroundColor: (c && c.length === 6 && '#' + c) || 'transparent'}); - } - - /** - * @param {external:jQuery} img - * @param {string} src The image source - * @returns {void} - */ - function setImg (img, src) { - if (isLessThanIE7 && (src.includes('AlphaBar.png') || src.includes('Bars.png') || src.includes('Maps.png'))) { - img.attr('pngSrc', src); - img.css({backgroundImage: 'none', filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + '\', sizingMethod=\'scale\')'}); - } else img.css({backgroundImage: 'url(\'' + src + '\')'}); - } - /** - * @param {external:jQuery} img - * @param {Float} y - * @returns {void} - */ - function setImgLoc (img, y) { - img.css({top: y + 'px'}); - } - /** - * @param {external:jQuery} obj - * @param {Float} alpha - * @returns {void} - */ - function setAlpha (obj, alpha) { - obj.css({visibility: alpha > 0 ? 'visible' : 'hidden'}); - if (alpha > 0 && alpha < 100) { - if (isLessThanIE7) { - const src = obj.attr('pngSrc'); - if (!isNullish(src) && ( - src.includes('AlphaBar.png') || src.includes('Bars.png') || src.includes('Maps.png') - )) { - obj.css({ - filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + - '\', sizingMethod=\'scale\') progid:DXImageTransform.Microsoft.Alpha(opacity=' + alpha + ')' - }); - } else obj.css({opacity: toFixedNumeric(alpha / 100, 4)}); - } else obj.css({opacity: toFixedNumeric(alpha / 100, 4)}); - } else if (alpha === 0 || alpha === 100) { - if (isLessThanIE7) { - const src = obj.attr('pngSrc'); - if (!isNullish(src) && ( - src.includes('AlphaBar.png') || src.includes('Bars.png') || src.includes('Maps.png') - )) { - obj.css({ - filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + src + - '\', sizingMethod=\'scale\')' - }); - } else obj.css({opacity: ''}); - } else obj.css({opacity: ''}); - } - } - - /** - * Revert color to original color when opened. - * @returns {void} - */ - function revertColor () { - color.active.val('ahex', color.current.val('ahex')); - } - /** - * Commit the color changes. - * @returns {void} - */ - function commitColor () { - color.current.val('ahex', color.active.val('ahex')); - } - /** - * @param {Event} e - * @returns {void} - */ - function radioClicked (e) { - $(this).parents('tbody:first').find('input:radio[value!="' + e.target.value + '"]').removeAttr('checked'); - setColorMode.call(that, e.target.value); - } - /** - * - * @returns {void} - */ - function currentClicked () { - revertColor.call(that); - } - /** - * - * @returns {void} - */ - function cancelClicked () { - revertColor.call(that); - settings.window.expandable && hide.call(that); - typeof cancelCallback === 'function' && cancelCallback.call(that, color.active, cancelButton); - } - /** - * - * @returns {void} - */ - function okClicked () { - commitColor.call(that); - settings.window.expandable && hide.call(that); - typeof commitCallback === 'function' && commitCallback.call(that, color.active, okButton); - } - /** - * - * @returns {void} - */ - function iconImageClicked () { - show.call(that); - } - /** - * @param {external:jQuery} ui - * @returns {void} - */ - function currentColorChanged (ui) { - const hex = ui.val('hex'); - currentPreview.css({backgroundColor: (hex && '#' + hex) || 'transparent'}); - setAlpha.call(that, currentPreview, toFixedNumeric(((ui.val('a') || 0) * 100) / 255, 4)); - } - /** - * @param {external:jQuery} ui - * @returns {void} - */ - function expandableColorChanged (ui) { - const hex = ui.val('hex'); - const va = ui.val('va'); - iconColor.css({backgroundColor: (hex && '#' + hex) || 'transparent'}); - setAlpha.call(that, iconAlpha, toFixedNumeric(((255 - ((va && va.a) || 0)) * 100) / 255, 4)); - if (settings.window.bindToInput && settings.window.updateInputColor) { - settings.window.input.css({ - backgroundColor: (hex && '#' + hex) || 'transparent', - color: isNullish(va) || va.v > 75 ? '#000000' : '#ffffff' - }); - } - } - /** - * @param {Event} e - * @returns {void} - */ - function moveBarMouseDown (e) { - // const {element} = settings.window, // local copies for YUI compressor - // {page} = settings.window; - elementStartX = Number.parseInt(container.css('left')); - elementStartY = Number.parseInt(container.css('top')); - pageStartX = e.pageX; - pageStartY = e.pageY; - // bind events to document to move window - we will unbind these on mouseup - $(document).bind('mousemove', documentMouseMove).bind('mouseup', documentMouseUp); - e.preventDefault(); // prevent attempted dragging of the column - } - /** - * @param {Event} e - * @returns {false} - */ - function documentMouseMove (e) { - container.css({ - left: elementStartX - (pageStartX - e.pageX) + 'px', - top: elementStartY - (pageStartY - e.pageY) + 'px' }); - if (settings.window.expandable && !$.support.boxModel) { - container.prev().css({ - left: container.css('left'), - top: container.css('top') - }); - } - e.stopPropagation(); - e.preventDefault(); - return false; - } - /** - * @param {Event} e - * @returns {false} - */ - function documentMouseUp (e) { - $(document).unbind('mousemove', documentMouseMove).unbind('mouseup', documentMouseUp); - e.stopPropagation(); - e.preventDefault(); - return false; - } - /** - * @param {Event} e - * @returns {false} - */ - function quickPickClicked (e) { - e.preventDefault(); - e.stopPropagation(); - color.active.val('ahex', $(this).attr('title') || null, e.target); - return false; - } - /** - * - * @returns {void} - */ - function show () { - color.current.val('ahex', color.active.val('ahex')); - /** - * - * @returns {void} - */ - function attachIFrame () { - if (!settings.window.expandable || $.support.boxModel) return; - const table = container.find('table:first'); - container.before('