[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 /** 2 * @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org> 3 * @license GNU General Public License version 2 or later; see LICENSE.txt 4 */ 5 6 /* eslint class-methods-use-this: ["error", { "exceptMethods": ["rgbToHex", "hslToRgb"] }] */ 7 (document => { 8 /** 9 * Regex for hex values e.g. #FF3929 10 * @type {RegExp} 11 */ 12 13 const hexRegex = /^#([a-z0-9]{1,2})([a-z0-9]{1,2})([a-z0-9]{1,2})$/i; 14 /** 15 * Regex for rgb values e.g. rgba(255, 0, 24, 0.5); 16 * @type {RegExp} 17 */ 18 19 const rgbRegex = /^rgba?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)(?:[\D]+([0-9](?:.\d+)?))?\)$/i; 20 /** 21 * Regex for hsl values e.g. hsl(255,0,24); 22 * @type {RegExp} 23 */ 24 25 const hslRegex = /^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$/i; 26 /** 27 * Regex for saturation and lightness of hsl - only accepts 1 or 0 or 0.4 or 40 28 * @type {RegExp} 29 */ 30 31 const hslNumberRegex = /^(([0-1])|(0\\.[0-9]+)|([0-9]{1,2})|(100))$/; 32 /** 33 * Regex for hue values - one to three numbers 34 * @type {RegExp} 35 */ 36 37 const hueRegex = /^[0-9]{1,3}$/; 38 /** 39 * Creates a slider for the color values hue, saturation and light. 40 * 41 * @since 4.0.0 42 */ 43 44 class JoomlaFieldColorSlider { 45 /** 46 * @param {HTMLElement} element 47 */ 48 constructor(element) { 49 // Elements 50 this.messageSpan = element.querySelector('.form-control-feedback'); 51 this.mainInput = element.querySelector('.color-input'); 52 this.input = element.querySelector('#slider-input'); 53 this.sliders = element.querySelectorAll('.color-slider'); 54 this.hueSlider = element.querySelector('#hue-slider'); 55 this.saturationSlider = element.querySelector('#saturation-slider'); 56 this.lightSlider = element.querySelector('#light-slider'); 57 this.alphaSlider = element.querySelector('#alpha-slider'); // Attributes 58 59 this.color = element.dataset.color || ''; 60 this.default = element.dataset.default || ''; 61 this.format = this.input.dataset.format || 'hex'; 62 this.saveFormat = this.mainInput.dataset.format || 'hex'; 63 this.preview = element.dataset.preview === 'true'; 64 this.setAlpha = this.format === 'hsla' || this.format === 'rgba'; 65 this.hue = 360; 66 this.saturation = 1; 67 this.light = 1; 68 this.alpha = 1; 69 this.defaultHsl = [this.hue, this.saturation, this.light, this.alpha]; 70 this.setInitValue(); 71 this.setBackground(); // Hide preview field, when selected value should not be visible 72 73 if (!this.preview) { 74 this.input.classList.add('hidden'); 75 } else { 76 this.setInputPattern(); 77 } // Always hide main input field (value saved in database) 78 79 80 this.mainInput.classList.add('hidden'); 81 Array.prototype.forEach.call(this.sliders, slider => { 82 slider.addEventListener('change', () => this.updateValue(slider)); 83 }); 84 this.input.addEventListener('change', () => this.changeInput(this.input)); 85 } 86 /** 87 * Set selected value into input field and set it as its background-color. 88 */ 89 90 91 updateValue(slider) { 92 this.showError(''); 93 const hsl = this.getSliderValueAsHsl(slider.value, slider.dataset.type); 94 const rgb = this.hslToRgb(hsl); 95 [this.hue, this.saturation, this.light, this.alpha] = hsl; 96 this.input.style.border = `2px solid $this.getRgbString(rgb)}`; 97 this.setSliderValues(hsl, slider.dataset.type); 98 this.setInputValue(hsl); 99 this.setBackground(slider); 100 } 101 /** 102 * React on user changing input value 103 * 104 * @param {HTMLElement} inputField 105 */ 106 107 108 changeInput(inputField) { 109 let hsl = [this.hue, this.saturation, this.light, this.alpha]; 110 111 if (!inputField.value) { 112 this.mainInput.value = ''; 113 this.showError(''); 114 return; 115 } 116 117 if (!this.checkValue(inputField.value)) { 118 this.showError('JFIELD_COLOR_ERROR_WRONG_FORMAT'); 119 this.setInputValue(this.defaultHsl); 120 } else { 121 this.showError(''); 122 123 switch (this.format) { 124 case 'hue': 125 hsl[0] = inputField.value; 126 this.hue = inputField.value; 127 break; 128 129 case 'saturation': 130 hsl[1] = inputField.value; 131 this.saturation = inputField.value; 132 break; 133 134 case 'light': 135 hsl[2] = inputField.value; 136 this.light = inputField.value; 137 break; 138 139 case 'alpha': 140 hsl[3] = inputField.value; 141 this.alpha = inputField.value; 142 break; 143 144 default: 145 hsl = this.getHsl(inputField.value); 146 } 147 148 this.setSliderValues(hsl); 149 this.setInputValue(hsl, true); 150 } 151 } 152 /** 153 * Check validity of value 154 * 155 * @param {number|string} value to check 156 * @param {string=false} format for which the value gets tested 157 * @returns {boolean} 158 */ 159 160 161 checkValue(value, format) { 162 const test = format || this.format; 163 164 switch (test) { 165 case 'hue': 166 return value <= 360 && hueRegex.test(value); 167 168 case 'saturation': 169 case 'light': 170 case 'alpha': 171 return hslNumberRegex.test(value); 172 173 case 'hsl': 174 case 'hsla': 175 return hslRegex.test(value); 176 177 case 'hex': 178 return hexRegex.test(value); 179 180 case 'rgb': 181 case 'rgba': 182 return rgbRegex.test(value); 183 184 default: 185 return false; 186 } 187 } 188 /** 189 * Set validation pattern on input field 190 */ 191 192 193 setInputPattern() { 194 let pattern; // RegExp has '/' at start and end 195 196 switch (this.format) { 197 case 'hue': 198 pattern = hueRegex.source.slice(1, -1); 199 break; 200 201 case 'saturation': 202 case 'light': 203 case 'alpha': 204 pattern = hslNumberRegex.source.slice(1, -1); 205 break; 206 207 case 'hsl': 208 case 'hsla': 209 pattern = hslRegex.source.slice(1, -1); 210 break; 211 212 case 'rgb': 213 pattern = rgbRegex.source.slice(1, -1); 214 break; 215 216 case 'hex': 217 default: 218 pattern = hexRegex.source.slice(1, -1); 219 } 220 221 this.input.setAttribute('pattern', pattern); 222 } 223 /** 224 * Set linear gradient for slider background 225 * @param {HTMLInputElement} [exceptSlider] 226 */ 227 228 229 setBackground(exceptSlider) { 230 Array.prototype.forEach.call(this.sliders, slider => { 231 // Jump over changed slider 232 if (exceptSlider === slider) { 233 return; 234 } 235 236 let colors = []; 237 let endValue = 100; // Longer start color so slider selection matches displayed colors 238 239 colors.push(this.getSliderValueAsRgb(0, slider.dataset.type)); 240 241 if (slider.dataset.type === 'hue') { 242 const steps = Math.floor(360 / 20); 243 endValue = 360; 244 245 for (let i = 0; i <= 360; i += steps) { 246 colors.push(this.getSliderValueAsRgb(i, slider.dataset.type)); 247 } 248 } else { 249 for (let i = 0; i <= 100; i += 10) { 250 colors.push(this.getSliderValueAsRgb(i, slider.dataset.type)); 251 } 252 } // Longer end color so slider selection matches displayed colors 253 254 255 colors.push(this.getSliderValueAsRgb(endValue, slider.dataset.type)); 256 colors = colors.map(value => this.getRgbString(value)); 257 slider.style.background = `linear-gradient(90deg, $colors.join(',')})`; 258 slider.style.webkitAppearance = 'none'; 259 }); 260 } 261 /** 262 * Convert given color into hue, saturation and light 263 */ 264 265 266 setInitValue() { 267 // The initial value can be also a color defined in css 268 const cssValue = window.getComputedStyle(this.input).getPropertyValue(this.default); 269 this.default = cssValue || this.default; 270 271 if (this.color === '' || typeof this.color === 'undefined') { 272 // Unable to get hsl with empty value 273 this.input.value = ''; 274 this.mainInput.value = ''; 275 return; 276 } 277 278 const value = this.checkValue(this.color, this.saveFormat) ? this.color : this.default; 279 280 if (!value) { 281 this.showError('JFIELD_COLOR_ERROR_NO_COLOUR'); 282 return; 283 } 284 285 let hsl = []; // When given value is a number, use it as defined format and get rest from default value 286 287 if (/^[0-9]+$/.test(value)) { 288 hsl = this.default && this.getHsl(this.default); 289 290 if (this.format === 'hue') { 291 hsl[0] = value; 292 } 293 294 if (this.format === 'saturation') { 295 hsl[1] = value > 1 ? value / 100 : value; 296 } 297 298 if (this.format === 'light') { 299 hsl[2] = value > 1 ? value / 100 : value; 300 } 301 302 if (this.format === 'alpha') { 303 hsl[3] = value > 1 ? value / 100 : value; 304 } 305 } else { 306 hsl = this.getHsl(value); 307 } 308 309 [this.hue, this.saturation, this.light] = hsl; 310 this.alpha = hsl[4] || this.alpha; 311 this.defaultHsl = this.default ? this.getHsl(this.default) : hsl; 312 this.setSliderValues(hsl); 313 this.setInputValue(hsl); 314 this.input.style.border = `2px solid $this.getRgbString(this.hslToRgb(hsl))}`; 315 } 316 /** 317 * Insert message into error message span 318 * Message gets handled with Joomla.Text or as empty string 319 * 320 * @param {string} msg 321 */ 322 323 324 showError(msg) { 325 this.messageSpan.innerText = msg ? Joomla.Text._(msg) : ''; 326 } 327 /** 328 * Convert value into HSLa e.g. #003E7C => [210, 100, 24] 329 * @param {array|number|string} value 330 * @returns {array} 331 */ 332 333 334 getHsl(value) { 335 let hsl = []; 336 337 if (Array.isArray(value)) { 338 hsl = value; 339 } else if (hexRegex.test(value)) { 340 hsl = this.hexToHsl(value); 341 } else if (rgbRegex.test(value)) { 342 hsl = this.rgbToHsl(value); 343 } else if (hslRegex.test(value)) { 344 const matches = value.match(hslRegex); 345 hsl = [matches[1], matches[2], matches[3], matches[4]]; 346 } else { 347 this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL'); 348 return this.defaultHsl; 349 } // Convert saturation etc. values from e.g. 40 to 0.4 350 351 352 let i; 353 354 for (i = 1; i < hsl.length; i += 1) { 355 hsl[i] = hsl[i] > 1 ? hsl[i] / 100 : hsl[i]; 356 } 357 358 return hsl; 359 } 360 /** 361 * Returns HSL value from color slider value 362 * @params {int} value convert this value 363 * @params {string} type type of value: hue, saturation, light or alpha 364 * @returns array 365 */ 366 367 368 getSliderValueAsHsl(value, type) { 369 let h = this.hue; 370 let s = this.saturation; 371 let l = this.light; 372 let a = this.alpha; 373 374 switch (type) { 375 case 'alpha': 376 a = value; 377 break; 378 379 case 'saturation': 380 s = value; 381 break; 382 383 case 'light': 384 l = value; 385 break; 386 387 case 'hue': 388 default: 389 h = value; 390 } // Percentage light and saturation 391 392 393 if (l > 1) { 394 l /= 100; 395 } 396 397 if (s > 1) { 398 s /= 100; 399 } 400 401 if (a > 1) { 402 a /= 100; 403 } 404 405 return [h, s, l, a]; 406 } 407 /** 408 * Calculates RGB value from color slider value 409 * @params {int} value convert this value 410 * @params {string} type type of value: hue, saturation, light or alpha 411 * @returns array 412 */ 413 414 415 getSliderValueAsRgb(value, type) { 416 return this.hslToRgb(this.getSliderValueAsHsl(value, type)); 417 } 418 /** 419 * Set value in all sliders 420 * @param {array} [hsla] 421 * @param {string} [except] 422 */ 423 424 425 setSliderValues([h, s, l, a], except) { 426 if (this.hueSlider && except !== 'hue') { 427 this.hueSlider.value = Math.round(h); 428 } 429 430 if (this.saturationSlider && except !== 'saturation') { 431 this.saturationSlider.value = Math.round(s * 100); 432 } 433 434 if (this.lightSlider && except !== 'light') { 435 this.lightSlider.value = Math.round(l * 100); 436 } 437 438 if (a && this.alphaSlider && except !== 'alpha') { 439 this.alphaSlider.value = Math.round(a * 100); 440 } 441 } 442 /** 443 * Set value in text input fields depending on their format 444 * @param {array} hsl 445 * @param {boolean=false} onlyMain indicates to change mainInput only 446 */ 447 448 449 setInputValue(hsl, onlyMain) { 450 const inputs = [this.mainInput]; 451 452 if (!onlyMain) { 453 inputs.push(this.input); 454 } 455 456 inputs.forEach(input => { 457 let value; 458 459 switch (input.dataset.format) { 460 case 'hsl': 461 value = this.getHslString(hsl); 462 break; 463 464 case 'hsla': 465 value = this.getHslString(hsl, true); 466 break; 467 468 case 'rgb': 469 value = this.getRgbString(this.hslToRgb(hsl)); 470 break; 471 472 case 'rgba': 473 value = this.getRgbString(this.hslToRgb(hsl), true); 474 break; 475 476 case 'hex': 477 value = this.rgbToHex(this.hslToRgb(hsl)); 478 break; 479 480 case 'alpha': 481 value = Math.round(hsl[3] * 100); 482 break; 483 484 case 'saturation': 485 value = Math.round(hsl[1] * 100); 486 break; 487 488 case 'light': 489 value = Math.round(hsl[2] * 100); 490 break; 491 492 case 'hue': 493 default: 494 value = Math.round(hsl[0]); 495 break; 496 } 497 498 input.value = value; 499 }); 500 } 501 /** 502 * Put RGB values into a string like 'rgb(<R>, <G>, <B>)' 503 * @params {array} rgba 504 * @params {boolean=false} withAlpha 505 * @return {string} 506 */ 507 508 509 getRgbString([r, g, b, a], withAlpha) { 510 if (withAlpha || this.setAlpha) { 511 const alpha = typeof a === 'undefined' ? this.alpha : a; 512 return `rgba($r}, $g}, $b}, $alpha})`; 513 } 514 515 return `rgb($r}, $g}, $b})`; 516 } 517 /** 518 * Put HSL values into a string like 'hsl(<H>, <S>%, <L>%, <a>)' 519 * @params {array} values 520 * @params {boolean=false} withAlpha 521 * @return {string} 522 */ 523 524 525 getHslString(values, withAlpha) { 526 let [h, s, l, a] = values; 527 s *= 100; 528 l *= 100; 529 [h, s, l] = [h, s, l].map(value => Math.round(value)); 530 531 if (withAlpha || this.setAlpha) { 532 a = a || this.alpha; 533 return `hsla($h}, $s}%, $l}%, $a})`; 534 } 535 536 return `hsl($h}, $s}%, $l}%)`; 537 } 538 /** 539 * Returns hsl values out of hex 540 * @param {array} rgb 541 * @return {string} 542 */ 543 544 545 rgbToHex(rgb) { 546 let r = rgb[0].toString(16).toUpperCase(); 547 let g = rgb[1].toString(16).toUpperCase(); 548 let b = rgb[2].toString(16).toUpperCase(); // Double value for hex with '#' and 6 chars 549 550 r = r.length === 1 ? `$r}$r}` : r; 551 g = g.length === 1 ? `$g}$g}` : g; 552 b = b.length === 1 ? `$b}$b}` : b; 553 return `#$r}$g}$b}`; 554 } 555 /** 556 * Returns hsl values out of rgb 557 * @param {string|array} values 558 * @return {array} 559 */ 560 561 562 rgbToHsl(values) { 563 let rgb = values; 564 565 if (typeof values === 'string') { 566 const parts = values.match(rgbRegex); 567 rgb = [parts[1], parts[2], parts[3], parts[4]]; 568 } 569 570 const [r, g, b] = rgb.map(value => value > 1 ? value / 255 : value); 571 const max = Math.max(r, g, b); 572 const min = Math.min(r, g, b); 573 const l = (max + min) / 2; 574 const d = max - min; 575 let h = 0; 576 let s = 0; 577 let a = rgb[3] || values[3] || this.alpha; 578 579 if (max !== min) { 580 if (max === 0) { 581 s = max; 582 } else if (min === 1) { 583 s = min; 584 } else { 585 s = (max - l) / Math.min(l, 1 - l); 586 } 587 588 switch (max) { 589 case r: 590 h = 60 * (g - b) / d; 591 break; 592 593 case g: 594 h = 60 * (2 + (b - r) / d); 595 break; 596 597 case b: 598 default: 599 h = 60 * (4 + (r - g) / d); 600 break; 601 } 602 } 603 604 h = h < 0 ? h + 360 : h; 605 a = a > 1 ? a / 100 : a; 606 return [h, s, l, a]; 607 } 608 /** 609 * Returns hsl values out of hex 610 * @param {string} hex 611 * @return {array} 612 */ 613 614 615 hexToHsl(hex) { 616 const parts = hex.match(hexRegex); 617 const r = parts[1]; 618 const g = parts[2]; 619 const b = parts[3]; 620 const rgb = [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]; 621 return this.rgbToHsl(rgb); 622 } 623 /** 624 * Convert HSLa values into RGBa 625 * @param {array} hsla 626 * @returns {number[]} 627 */ 628 629 630 hslToRgb([h, sat, light, alpha]) { 631 let r = 1; 632 let g = 1; 633 let b = 1; // Saturation and light were calculated as 0.24 instead of 24% 634 635 const s = sat > 1 ? sat / 100 : sat; 636 const l = light > 1 ? light / 100 : light; 637 const a = alpha > 1 ? alpha / 100 : alpha; 638 639 if (h < 0 || h > 360 || s < 0 || s > 1 || l < 0 || l > 1) { 640 this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL'); 641 return this.hslToRgb(this.defaultHsl); 642 } 643 644 const c = (1 - Math.abs(2 * l - 1)) * s; 645 const hi = h / 60; 646 const x = c * (1 - Math.abs(hi % 2 - 1)); 647 const m = l - c / 2; 648 649 if (h >= 0 && h < 60) { 650 [r, g, b] = [c, x, 0]; 651 } else if (h >= 60 && h < 120) { 652 [r, g, b] = [x, c, 0]; 653 } else if (h >= 120 && h < 180) { 654 [r, g, b] = [0, c, x]; 655 } else if (h >= 180 && h < 240) { 656 [r, g, b] = [0, x, c]; 657 } else if (h >= 240 && h < 300) { 658 [r, g, b] = [x, 0, c]; 659 } else if (h >= 300 && h <= 360) { 660 [r, g, b] = [c, 0, x]; 661 } else { 662 this.showError('JFIELD_COLOR_ERROR_CONVERT_HUE'); 663 return this.hslToRgb(this.defaultHsl); 664 } 665 666 const rgb = [r, g, b].map(value => Math.round((value + m) * 255)); 667 rgb.push(a); 668 return rgb; 669 } 670 671 } 672 673 document.addEventListener('DOMContentLoaded', () => { 674 const fields = document.querySelectorAll('.color-slider-wrapper'); 675 676 if (fields) { 677 Array.prototype.forEach.call(fields, slider => { 678 // eslint-disable-next-line no-new 679 new JoomlaFieldColorSlider(slider); 680 }); 681 } 682 }); 683 })(document);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |