/home/preegmxb/byeaglytics-co.com/media/system/js/fields/joomla-field-color-slider.js
/**
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["rgbToHex", "hslToRgb"] }] */
(document => {
/**
* Regex for hex values e.g. #FF3929
* @type {RegExp}
*/
const hexRegex = new RegExp(/^#([a-z0-9]{1,2})([a-z0-9]{1,2})([a-z0-9]{1,2})$/i);
/**
* Regex for rgb values e.g. rgba(255, 0, 24, 0.5);
* @type {RegExp}
*/
const rgbRegex = new RegExp(/^rgba?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)(?:[\D]+([0-9](?:.\d+)?))?\)$/i);
/**
* Regex for hsl values e.g. hsl(255,0,24);
* @type {RegExp}
*/
const hslRegex = new RegExp(/^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$/i);
/**
* Regex for saturation and lightness of hsl - only accepts 1 or 0 or 0.4 or 40
* @type {RegExp}
*/
const hslNumberRegex = new RegExp(/^(([0-1])|(0\\.[0-9]+)|([0-9]{1,2})|(100))$/);
/**
* Regex for hue values - one to three numbers
* @type {RegExp}
*/
const hueRegex = new RegExp(/^[0-9]{1,3}$/);
/**
* Creates a slider for the color values hue, saturation and light.
*
* @since 4.0.0
*/
class JoomlaFieldColorSlider {
/**
* @param {HTMLElement} element
*/
constructor(element) {
// Elements
this.messageSpan = element.querySelector('.form-control-feedback');
this.mainInput = element.querySelector('.color-input');
this.input = element.querySelector('#slider-input');
this.sliders = element.querySelectorAll('.color-slider');
this.hueSlider = element.querySelector('#hue-slider');
this.saturationSlider = element.querySelector('#saturation-slider');
this.lightSlider = element.querySelector('#light-slider');
this.alphaSlider = element.querySelector('#alpha-slider'); // Attributes
this.color = element.dataset.color || '';
this.default = element.dataset.default || '';
this.format = this.input.dataset.format || 'hex';
this.saveFormat = this.mainInput.dataset.format || 'hex';
this.preview = element.dataset.preview === 'true';
this.setAlpha = this.format === 'hsla' || this.format === 'rgba';
this.hue = 360;
this.saturation = 1;
this.light = 1;
this.alpha = 1;
this.defaultHsl = [this.hue, this.saturation, this.light, this.alpha];
this.setInitValue();
this.setBackground(); // Hide preview field, when selected value should not be visible
if (!this.preview) {
this.input.classList.add('hidden');
} else {
this.setInputPattern();
} // Always hide main input field (value saved in database)
this.mainInput.classList.add('hidden');
Array.prototype.forEach.call(this.sliders, slider => {
slider.addEventListener('change', () => this.updateValue(slider));
});
this.input.addEventListener('change', () => this.changeInput(this.input));
}
/**
* Set selected value into input field and set it as its background-color.
*/
updateValue(slider) {
this.showError('');
const hsl = this.getSliderValueAsHsl(slider.value, slider.dataset.type);
const rgb = this.hslToRgb(hsl);
[this.hue, this.saturation, this.light, this.alpha] = hsl;
this.input.style.border = `2px solid ${this.getRgbString(rgb)}`;
this.setSliderValues(hsl, slider.dataset.type);
this.setInputValue(hsl);
this.setBackground(slider);
}
/**
* React on user changing input value
*
* @param {HTMLElement} inputField
*/
changeInput(inputField) {
let hsl = [this.hue, this.saturation, this.light, this.alpha];
if (!inputField.value) {
this.mainInput.value = '';
this.showError('');
return;
}
if (!this.checkValue(inputField.value)) {
this.showError('JFIELD_COLOR_ERROR_WRONG_FORMAT');
this.setInputValue(this.defaultHsl);
} else {
this.showError('');
switch (this.format) {
case 'hue':
hsl[0] = inputField.value;
this.hue = inputField.value;
break;
case 'saturation':
hsl[1] = inputField.value;
this.saturation = inputField.value;
break;
case 'light':
hsl[2] = inputField.value;
this.light = inputField.value;
break;
case 'alpha':
hsl[3] = inputField.value;
this.alpha = inputField.value;
break;
default:
hsl = this.getHsl(inputField.value);
}
this.setSliderValues(hsl);
this.setInputValue(hsl, true);
}
}
/**
* Check validity of value
*
* @param {number|string} value to check
* @param {string=false} format for which the value gets tested
* @returns {boolean}
*/
checkValue(value, format) {
const test = format || this.format;
switch (test) {
case 'hue':
return value <= 360 && hueRegex.test(value);
case 'saturation':
case 'light':
case 'alpha':
return hslNumberRegex.test(value);
case 'hsl':
case 'hsla':
return hslRegex.test(value);
case 'hex':
return hexRegex.test(value);
case 'rgb':
case 'rgba':
return rgbRegex.test(value);
default:
return false;
}
}
/**
* Set validation pattern on input field
*/
setInputPattern() {
let pattern; // RegExp has '/' at start and end
switch (this.format) {
case 'hue':
pattern = hueRegex.source.slice(1, -1);
break;
case 'saturation':
case 'light':
case 'alpha':
pattern = hslNumberRegex.source.slice(1, -1);
break;
case 'hsl':
case 'hsla':
pattern = hslRegex.source.slice(1, -1);
break;
case 'rgb':
pattern = rgbRegex.source.slice(1, -1);
break;
case 'hex':
default:
pattern = hexRegex.source.slice(1, -1);
}
this.input.setAttribute('pattern', pattern);
}
/**
* Set linear gradient for slider background
* @param {HTMLInputElement} [exceptSlider]
*/
setBackground(exceptSlider) {
Array.prototype.forEach.call(this.sliders, slider => {
// Jump over changed slider
if (exceptSlider === slider) {
return;
}
let colors = [];
let endValue = 100; // Longer start color so slider selection matches displayed colors
colors.push(this.getSliderValueAsRgb(0, slider.dataset.type));
if (slider.dataset.type === 'hue') {
const steps = Math.floor(360 / 20);
endValue = 360;
for (let i = 0; i <= 360; i += steps) {
colors.push(this.getSliderValueAsRgb(i, slider.dataset.type));
}
} else {
for (let i = 0; i <= 100; i += 10) {
colors.push(this.getSliderValueAsRgb(i, slider.dataset.type));
}
} // Longer end color so slider selection matches displayed colors
colors.push(this.getSliderValueAsRgb(endValue, slider.dataset.type));
colors = colors.map(value => this.getRgbString(value));
slider.style.background = `linear-gradient(90deg, ${colors.join(',')})`;
slider.style.webkitAppearance = 'none';
});
}
/**
* Convert given color into hue, saturation and light
*/
setInitValue() {
// The initial value can be also a color defined in css
const cssValue = window.getComputedStyle(this.input).getPropertyValue(this.default);
this.default = cssValue || this.default;
if (this.color === '' || typeof this.color === 'undefined') {
// Unable to get hsl with empty value
this.input.value = '';
this.mainInput.value = '';
return;
}
const value = this.checkValue(this.color, this.saveFormat) ? this.color : this.default;
if (!value) {
this.showError('JFIELD_COLOR_ERROR_NO_COLOUR');
return;
}
let hsl = []; // When given value is a number, use it as defined format and get rest from default value
if (/^[0-9]+$/.test(value)) {
hsl = this.default && this.getHsl(this.default);
if (this.format === 'hue') {
hsl[0] = value;
}
if (this.format === 'saturation') {
hsl[1] = value > 1 ? value / 100 : value;
}
if (this.format === 'light') {
hsl[2] = value > 1 ? value / 100 : value;
}
if (this.format === 'alpha') {
hsl[3] = value > 1 ? value / 100 : value;
}
} else {
hsl = this.getHsl(value);
}
[this.hue, this.saturation, this.light] = hsl;
this.alpha = hsl[4] || this.alpha;
this.defaultHsl = this.default ? this.getHsl(this.default) : hsl;
this.setSliderValues(hsl);
this.setInputValue(hsl);
this.input.style.border = `2px solid ${this.getRgbString(this.hslToRgb(hsl))}`;
}
/**
* Insert message into error message span
* Message gets handled with Joomla.Text or as empty string
*
* @param {string} msg
*/
showError(msg) {
this.messageSpan.innerText = msg ? Joomla.Text._(msg) : '';
}
/**
* Convert value into HSLa e.g. #003E7C => [210, 100, 24]
* @param {array|number|string} value
* @returns {array}
*/
getHsl(value) {
let hsl = [];
if (Array.isArray(value)) {
hsl = value;
} else if (hexRegex.test(value)) {
hsl = this.hexToHsl(value);
} else if (rgbRegex.test(value)) {
hsl = this.rgbToHsl(value);
} else if (hslRegex.test(value)) {
const matches = value.match(hslRegex);
hsl = [matches[1], matches[2], matches[3], matches[4]];
} else {
this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL');
return this.defaultHsl;
} // Convert saturation etc. values from e.g. 40 to 0.4
let i;
for (i = 1; i < hsl.length; i += 1) {
hsl[i] = hsl[i] > 1 ? hsl[i] / 100 : hsl[i];
}
return hsl;
}
/**
* Returns HSL value from color slider value
* @params {int} value convert this value
* @params {string} type type of value: hue, saturation, light or alpha
* @returns array
*/
getSliderValueAsHsl(value, type) {
let h = this.hue;
let s = this.saturation;
let l = this.light;
let a = this.alpha;
switch (type) {
case 'alpha':
a = value;
break;
case 'saturation':
s = value;
break;
case 'light':
l = value;
break;
case 'hue':
default:
h = value;
} // Percentage light and saturation
if (l > 1) {
l /= 100;
}
if (s > 1) {
s /= 100;
}
if (a > 1) {
a /= 100;
}
return [h, s, l, a];
}
/**
* Calculates RGB value from color slider value
* @params {int} value convert this value
* @params {string} type type of value: hue, saturation, light or alpha
* @returns array
*/
getSliderValueAsRgb(value, type) {
return this.hslToRgb(this.getSliderValueAsHsl(value, type));
}
/**
* Set value in all sliders
* @param {array} [hsla]
* @param {string} [except]
*/
setSliderValues([h, s, l, a], except) {
if (this.hueSlider && except !== 'hue') {
this.hueSlider.value = Math.round(h);
}
if (this.saturationSlider && except !== 'saturation') {
this.saturationSlider.value = Math.round(s * 100);
}
if (this.lightSlider && except !== 'light') {
this.lightSlider.value = Math.round(l * 100);
}
if (a && this.alphaSlider && except !== 'alpha') {
this.alphaSlider.value = Math.round(a * 100);
}
}
/**
* Set value in text input fields depending on their format
* @param {array} hsl
* @param {boolean=false} onlyMain indicates to change mainInput only
*/
setInputValue(hsl, onlyMain) {
const inputs = [this.mainInput];
if (!onlyMain) {
inputs.push(this.input);
}
inputs.forEach(input => {
let value;
switch (input.dataset.format) {
case 'hsl':
value = this.getHslString(hsl);
break;
case 'hsla':
value = this.getHslString(hsl, true);
break;
case 'rgb':
value = this.getRgbString(this.hslToRgb(hsl));
break;
case 'rgba':
value = this.getRgbString(this.hslToRgb(hsl), true);
break;
case 'hex':
value = this.rgbToHex(this.hslToRgb(hsl));
break;
case 'alpha':
value = Math.round(hsl[3] * 100);
break;
case 'saturation':
value = Math.round(hsl[1] * 100);
break;
case 'light':
value = Math.round(hsl[2] * 100);
break;
case 'hue':
default:
value = Math.round(hsl[0]);
break;
}
input.value = value;
});
}
/**
* Put RGB values into a string like 'rgb(<R>, <G>, <B>)'
* @params {array} rgba
* @params {boolean=false} withAlpha
* @return {string}
*/
getRgbString([r, g, b, a], withAlpha) {
if (withAlpha || this.setAlpha) {
const alpha = typeof a === 'undefined' ? this.alpha : a;
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
return `rgb(${r}, ${g}, ${b})`;
}
/**
* Put HSL values into a string like 'hsl(<H>, <S>%, <L>%, <a>)'
* @params {array} values
* @params {boolean=false} withAlpha
* @return {string}
*/
getHslString(values, withAlpha) {
let [h, s, l, a] = values;
s *= 100;
l *= 100;
[h, s, l] = [h, s, l].map(value => Math.round(value));
if (withAlpha || this.setAlpha) {
a = a || this.alpha;
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
}
return `hsl(${h}, ${s}%, ${l}%)`;
}
/**
* Returns hsl values out of hex
* @param {array} rgb
* @return {string}
*/
rgbToHex(rgb) {
let r = rgb[0].toString(16).toUpperCase();
let g = rgb[1].toString(16).toUpperCase();
let b = rgb[2].toString(16).toUpperCase(); // Double value for hex with '#' and 6 chars
r = r.length === 1 ? `${r}${r}` : r;
g = g.length === 1 ? `${g}${g}` : g;
b = b.length === 1 ? `${b}${b}` : b;
return `#${r}${g}${b}`;
}
/**
* Returns hsl values out of rgb
* @param {string|array} values
* @return {array}
*/
rgbToHsl(values) {
let rgb = values;
if (typeof values === 'string') {
const parts = values.match(rgbRegex);
rgb = [parts[1], parts[2], parts[3], parts[4]];
}
const [r, g, b] = rgb.map(value => value > 1 ? value / 255 : value);
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const l = (max + min) / 2;
const d = max - min;
let h = 0;
let s = 0;
let a = rgb[3] || values[3] || this.alpha;
if (max !== min) {
if (max === 0) {
s = max;
} else if (min === 1) {
s = min;
} else {
s = (max - l) / Math.min(l, 1 - l);
}
switch (max) {
case r:
h = 60 * (g - b) / d;
break;
case g:
h = 60 * (2 + (b - r) / d);
break;
case b:
default:
h = 60 * (4 + (r - g) / d);
break;
}
}
h = h < 0 ? h + 360 : h;
a = a > 1 ? a / 100 : a;
return [h, s, l, a];
}
/**
* Returns hsl values out of hex
* @param {string} hex
* @return {array}
*/
hexToHsl(hex) {
const parts = hex.match(hexRegex);
const r = parts[1];
const g = parts[2];
const b = parts[3];
const rgb = [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
return this.rgbToHsl(rgb);
}
/**
* Convert HSLa values into RGBa
* @param {array} hsla
* @returns {number[]}
*/
hslToRgb([h, sat, light, alpha]) {
let r = 1;
let g = 1;
let b = 1; // Saturation and light were calculated as 0.24 instead of 24%
const s = sat > 1 ? sat / 100 : sat;
const l = light > 1 ? light / 100 : light;
const a = alpha > 1 ? alpha / 100 : alpha;
if (h < 0 || h > 360 || s < 0 || s > 1 || l < 0 || l > 1) {
this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL');
return this.hslToRgb(this.defaultHsl);
}
const c = (1 - Math.abs(2 * l - 1)) * s;
const hi = h / 60;
const x = c * (1 - Math.abs(hi % 2 - 1));
const m = l - c / 2;
if (h >= 0 && h < 60) {
[r, g, b] = [c, x, 0];
} else if (h >= 60 && h < 120) {
[r, g, b] = [x, c, 0];
} else if (h >= 120 && h < 180) {
[r, g, b] = [0, c, x];
} else if (h >= 180 && h < 240) {
[r, g, b] = [0, x, c];
} else if (h >= 240 && h < 300) {
[r, g, b] = [x, 0, c];
} else if (h >= 300 && h <= 360) {
[r, g, b] = [c, 0, x];
} else {
this.showError('JFIELD_COLOR_ERROR_CONVERT_HUE');
return this.hslToRgb(this.defaultHsl);
}
const rgb = [r, g, b].map(value => Math.round((value + m) * 255));
rgb.push(a);
return rgb;
}
}
document.addEventListener('DOMContentLoaded', () => {
const fields = document.querySelectorAll('.color-slider-wrapper');
if (fields) {
Array.prototype.forEach.call(fields, slider => {
// eslint-disable-next-line no-new
new JoomlaFieldColorSlider(slider);
});
}
});
})(document);