[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/media/system/js/fields/ -> joomla-field-color-slider.js (source)

   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);


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer