Overhauled color picker with RGBA and HSL
authorAlexander Ebert <ebert@woltlab.com>
Mon, 30 May 2022 14:54:12 +0000 (16:54 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 30 May 2022 14:54:12 +0000 (16:54 +0200)
ts/WoltLabSuite/Core/Ui/Color/Picker.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Color/Picker.js
wcfsetup/install/files/style/ui/colorPicker.scss
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index cb564b5c690d7d007ed81c937a7e087252d2c2dd..b217807f0e96f41ca2f0ca5eb0f65eb1c997cf7d 100644 (file)
@@ -19,9 +19,23 @@ import * as ColorUtil from "../../ColorUtil";
 type CallbackSubmit = (data: ColorUtil.RGBA) => void;
 
 const enum Channel {
-  R,
-  G,
-  B,
+  R = "r",
+  G = "g",
+  B = "b",
+  A = "a",
+}
+
+const enum HSL {
+  Hue = "hue",
+  Saturation = "saturation",
+  Lightness = "lightness",
+}
+
+const enum ColorSource {
+  HEX = "hex",
+  HSL = "hsl",
+  RGBA = "rgba",
+  Setup = "setup",
 }
 
 interface ColorPickerOptions {
@@ -29,14 +43,15 @@ interface ColorPickerOptions {
 }
 
 class UiColorPicker implements DialogCallbackObject {
-  protected alphaInput: HTMLInputElement | null = null;
   private readonly channels = new Map<Channel, HTMLInputElement>();
   protected colorInput: HTMLInputElement | null = null;
   protected colorTextInput: HTMLInputElement | null = null;
   protected readonly element: HTMLElement;
+  private readonly hsl = new Map<HSL, HTMLInputElement>();
+  private hslContainer?: HTMLElement = undefined;
   protected readonly input: HTMLInputElement;
-  protected newColor: HTMLSpanElement | null = null;
-  protected oldColor: HTMLSpanElement | null = null;
+  protected newColor?: HTMLElement = undefined;
+  protected oldColor?: HTMLElement = undefined;
   protected readonly options: ColorPickerOptions;
 
   /**
@@ -70,29 +85,38 @@ class UiColorPicker implements DialogCallbackObject {
       id: `${DomUtil.identify(this.element)}_colorPickerDialog`,
       source: `
 <div class="colorPickerDialog">
-  <div class="row rowColGap formGrid">
-    <div class="col-xs-12 col-md-8">
-      <dl>
-        <dt>${Language.get("wcf.style.colorPicker.color")}</dt>
+  <div class="colorPickerHsvContainer" style="--hue: 0; --saturation: 0%; --lightness: 0%">
+    <dl>
+        <dt>${Language.get("wcf.style.colorPicker.hue")}</dt>
         <dd>
-          <div class="inputAddon colorPickerChannel">
-            <span class="inputPrefix">R</span>
-            <input type="number" min="0" max="255" data-channel="r">
-          </div>
-          <div class="inputAddon colorPickerChannel">
-            <span class="inputPrefix">G</span>
-            <input type="number" min="0" max="255" data-channel="g">
-          </div>
-          <div class="inputAddon colorPickerChannel">
-            <span class="inputPrefix">B</span>
-            <input type="number" min="0" max="255" data-channel="b">
-          </div>
+          <input type="range" min="0" max="359" class="colorPickerHslRange" data-coordinate="hue">
         </dd>
-      </dl>
-      <dl>
-        <dt>${Language.get("wcf.style.colorPicker.alpha")}</dt>
+    </dl>
+    <dl>
+        <dt>${Language.get("wcf.style.colorPicker.saturation")}</dt>
         <dd>
-          <input type="range" min="0" max="1" step="0.01">
+          <input type="range" min="0" max="100" class="colorPickerHslRange" data-coordinate="saturation">
+        </dd>
+    </dl>
+    <dl>
+        <dt>${Language.get("wcf.style.colorPicker.lightness")}</dt>
+        <dd>
+          <input type="range" min="0" max="100" class="colorPickerHslRange" data-coordinate="lightness">
+        </dd>
+    </dl>
+  </div>
+  <div class="colorPickerValueContainer">
+    <div>
+      <dl>
+        <dt>${Language.get("wcf.style.colorPicker.color")}</dt>
+        <dd class="colorPickerChannels">
+          rgba(
+          <input type="number" min="0" max="255" class="colorPickerChannel" data-channel="r">
+          <input type="number" min="0" max="255" class="colorPickerChannel" data-channel="g">
+          <input type="number" min="0" max="255" class="colorPickerChannel" data-channel="b">
+          /
+          <input type="number" min="0" max="1" step="0.01" class="colorPickerChannel" data-channel="a">
+          )
         </dd>
       </dl>
       <dl>
@@ -105,7 +129,7 @@ class UiColorPicker implements DialogCallbackObject {
         </dd>
       </dl>
     </div>
-    <div class="col-xs-12 col-md-4 colorPickerComparison">
+    <div class="colorPickerComparison">
       <small>${Language.get("wcf.style.colorPicker.new")}</small>
       <div class="colorPickerColorNew">
         <span style="background-color: ${this.input.value}"></span>
@@ -117,7 +141,7 @@ class UiColorPicker implements DialogCallbackObject {
     </div>
   </div>
   <div class="formSubmit">
-    <button class="buttonPrimary">${Language.get("wcf.style.colorPicker.button.apply")}</button>
+    <button class="button buttonPrimary">${Language.get("wcf.style.colorPicker.button.apply")}</button>
   </div>
 </div>`,
       options: {
@@ -125,15 +149,24 @@ class UiColorPicker implements DialogCallbackObject {
           this.channels.set(Channel.R, content.querySelector('input[data-channel="r"]') as HTMLInputElement);
           this.channels.set(Channel.G, content.querySelector('input[data-channel="g"]') as HTMLInputElement);
           this.channels.set(Channel.B, content.querySelector('input[data-channel="b"]') as HTMLInputElement);
+          this.channels.set(Channel.A, content.querySelector('input[data-channel="a"]') as HTMLInputElement);
           this.channels.forEach((input) => {
-            input.addEventListener("input", () => this.updateColor());
+            input.addEventListener("input", () => this.updateColor(ColorSource.RGBA));
           });
 
-          this.alphaInput = content.querySelector("input[type=range]") as HTMLInputElement;
-          this.alphaInput.addEventListener("input", () => this.updateColor());
+          this.hslContainer = content.querySelector(".colorPickerHsvContainer") as HTMLElement;
+          this.hsl.set(HSL.Hue, content.querySelector('input[data-coordinate="hue"]') as HTMLInputElement);
+          this.hsl.set(
+            HSL.Saturation,
+            content.querySelector('input[data-coordinate="saturation"]') as HTMLInputElement,
+          );
+          this.hsl.set(HSL.Lightness, content.querySelector('input[data-coordinate="lightness"]') as HTMLInputElement);
+          this.hsl.forEach((input) => {
+            input.addEventListener("input", () => this.updateColor(ColorSource.HSL));
+          });
 
-          this.newColor = content.querySelector(".colorPickerColorNew > span") as HTMLSpanElement;
-          this.oldColor = content.querySelector(".colorPickerColorOld > span") as HTMLSpanElement;
+          this.newColor = content.querySelector(".colorPickerColorNew > span") as HTMLElement;
+          this.oldColor = content.querySelector(".colorPickerColorOld > span") as HTMLElement;
 
           this.colorTextInput = content.querySelector("input[type=text]") as HTMLInputElement;
           this.colorTextInput.addEventListener("blur", (ev) => this.updateColorFromHex(ev));
@@ -169,8 +202,8 @@ class UiColorPicker implements DialogCallbackObject {
    *
    * @since 5.5
    */
-  protected updateColor(): void {
-    this.setColor(this.getColor());
+  protected updateColor(source: ColorSource): void {
+    this.setColor(this.getColor(source), source);
   }
 
   /**
@@ -196,7 +229,7 @@ class UiColorPicker implements DialogCallbackObject {
       }
     }
 
-    this.setColor(color);
+    this.setColor(color, ColorSource.HEX);
   }
 
   /**
@@ -204,12 +237,27 @@ class UiColorPicker implements DialogCallbackObject {
    *
    * @since 5.5
    */
-  protected getColor(): ColorUtil.RGBA {
+  protected getColor(source: ColorSource): ColorUtil.RGBA {
+    const a = parseFloat(this.channels.get(Channel.A)!.value);
+
+    if (source === ColorSource.HSL) {
+      const rgb = ColorUtil.hslToRgb(
+        parseInt(this.hsl.get(HSL.Hue)!.value, 10),
+        parseInt(this.hsl.get(HSL.Saturation)!.value, 10),
+        parseInt(this.hsl.get(HSL.Lightness)!.value, 10),
+      );
+
+      return {
+        ...rgb,
+        a,
+      };
+    }
+
     return {
       r: parseInt(this.channels.get(Channel.R)!.value, 10),
       g: parseInt(this.channels.get(Channel.G)!.value, 10),
       b: parseInt(this.channels.get(Channel.B)!.value, 10),
-      a: parseInt(this.alphaInput!.value, 10),
+      a,
     };
   }
 
@@ -227,18 +275,36 @@ class UiColorPicker implements DialogCallbackObject {
    *
    * @since 5.5
    */
-  protected setColor(color: ColorUtil.RGBA | string): void {
+  protected setColor(color: ColorUtil.RGBA | string, source: ColorSource): void {
     if (typeof color === "string") {
       color = ColorUtil.stringToRgba(color);
     }
 
-    this.channels.get(Channel.R)!.value = color.r.toString();
-    this.channels.get(Channel.G)!.value = color.g.toString();
-    this.channels.get(Channel.B)!.value = color.b.toString();
-    this.alphaInput!.value = color.a.toString();
+    const { r, g, b, a } = color;
+    const { h, s, l } = ColorUtil.rgbToHsl(r, g, b);
+
+    if (source !== ColorSource.HSL) {
+      this.hsl.get(HSL.Hue)!.value = h.toString();
+      this.hsl.get(HSL.Saturation)!.value = s.toString();
+      this.hsl.get(HSL.Lightness)!.value = l.toString();
+    }
+
+    this.hslContainer!.style.setProperty(`--${HSL.Hue}`, `${h}`);
+    this.hslContainer!.style.setProperty(`--${HSL.Saturation}`, `${s}%`);
+    this.hslContainer!.style.setProperty(`--${HSL.Lightness}`, `${l}%`);
+
+    if (source !== ColorSource.RGBA) {
+      this.channels.get(Channel.R)!.value = r.toString();
+      this.channels.get(Channel.G)!.value = g.toString();
+      this.channels.get(Channel.B)!.value = b.toString();
+      this.channels.get(Channel.A)!.value = a.toString();
+    }
 
     this.newColor!.style.backgroundColor = ColorUtil.rgbaToString(color);
-    this.colorTextInput!.value = ColorUtil.rgbaToHex(color);
+
+    if (source !== ColorSource.HEX) {
+      this.colorTextInput!.value = ColorUtil.rgbaToHex(color);
+    }
   }
 
   /**
@@ -251,7 +317,7 @@ class UiColorPicker implements DialogCallbackObject {
       color = ColorUtil.stringToRgba(color);
     }
 
-    this.setColor(color);
+    this.setColor(color, ColorSource.Setup);
 
     this.oldColor!.style.backgroundColor = ColorUtil.rgbaToString(color);
   }
@@ -262,7 +328,7 @@ class UiColorPicker implements DialogCallbackObject {
    * @since 5.5
    */
   protected submitDialog(): void {
-    const color = this.getColor();
+    const color = this.getColor(ColorSource.RGBA);
     const colorString = ColorUtil.rgbaToString(color);
 
     this.oldColor!.style.backgroundColor = colorString;
index 9fcd975eee97498bbc5b70be0395586f14f5017b..1832dc180e486578fa7601d98f637108179cd8d8 100644 (file)
@@ -20,12 +20,13 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
          * Initializes a new color picker instance.
          */
         constructor(element, options) {
-            this.alphaInput = null;
             this.channels = new Map();
             this.colorInput = null;
             this.colorTextInput = null;
-            this.newColor = null;
-            this.oldColor = null;
+            this.hsl = new Map();
+            this.hslContainer = undefined;
+            this.newColor = undefined;
+            this.oldColor = undefined;
             if (!(element instanceof Element)) {
                 throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");
             }
@@ -44,29 +45,38 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
                 id: `${Util_1.default.identify(this.element)}_colorPickerDialog`,
                 source: `
 <div class="colorPickerDialog">
-  <div class="row rowColGap formGrid">
-    <div class="col-xs-12 col-md-8">
-      <dl>
-        <dt>${Language.get("wcf.style.colorPicker.color")}</dt>
+  <div class="colorPickerHsvContainer" style="--hue: 0; --saturation: 0%; --lightness: 0%">
+    <dl>
+        <dt>${Language.get("wcf.style.colorPicker.hue")}</dt>
         <dd>
-          <div class="inputAddon colorPickerChannel">
-            <span class="inputPrefix">R</span>
-            <input type="number" min="0" max="255" data-channel="r">
-          </div>
-          <div class="inputAddon colorPickerChannel">
-            <span class="inputPrefix">G</span>
-            <input type="number" min="0" max="255" data-channel="g">
-          </div>
-          <div class="inputAddon colorPickerChannel">
-            <span class="inputPrefix">B</span>
-            <input type="number" min="0" max="255" data-channel="b">
-          </div>
+          <input type="range" min="0" max="359" class="colorPickerHslRange" data-coordinate="hue">
         </dd>
-      </dl>
-      <dl>
-        <dt>${Language.get("wcf.style.colorPicker.alpha")}</dt>
+    </dl>
+    <dl>
+        <dt>${Language.get("wcf.style.colorPicker.saturation")}</dt>
+        <dd>
+          <input type="range" min="0" max="100" class="colorPickerHslRange" data-coordinate="saturation">
+        </dd>
+    </dl>
+    <dl>
+        <dt>${Language.get("wcf.style.colorPicker.lightness")}</dt>
         <dd>
-          <input type="range" min="0" max="1" step="0.01">
+          <input type="range" min="0" max="100" class="colorPickerHslRange" data-coordinate="lightness">
+        </dd>
+    </dl>
+  </div>
+  <div class="colorPickerValueContainer">
+    <div>
+      <dl>
+        <dt>${Language.get("wcf.style.colorPicker.color")}</dt>
+        <dd class="colorPickerChannels">
+          rgba(
+          <input type="number" min="0" max="255" class="colorPickerChannel" data-channel="r">
+          <input type="number" min="0" max="255" class="colorPickerChannel" data-channel="g">
+          <input type="number" min="0" max="255" class="colorPickerChannel" data-channel="b">
+          /
+          <input type="number" min="0" max="1" step="0.01" class="colorPickerChannel" data-channel="a">
+          )
         </dd>
       </dl>
       <dl>
@@ -79,7 +89,7 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
         </dd>
       </dl>
     </div>
-    <div class="col-xs-12 col-md-4 colorPickerComparison">
+    <div class="colorPickerComparison">
       <small>${Language.get("wcf.style.colorPicker.new")}</small>
       <div class="colorPickerColorNew">
         <span style="background-color: ${this.input.value}"></span>
@@ -91,19 +101,25 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
     </div>
   </div>
   <div class="formSubmit">
-    <button class="buttonPrimary">${Language.get("wcf.style.colorPicker.button.apply")}</button>
+    <button class="button buttonPrimary">${Language.get("wcf.style.colorPicker.button.apply")}</button>
   </div>
 </div>`,
                 options: {
                     onSetup: (content) => {
-                        this.channels.set(0 /* R */, content.querySelector('input[data-channel="r"]'));
-                        this.channels.set(1 /* G */, content.querySelector('input[data-channel="g"]'));
-                        this.channels.set(2 /* B */, content.querySelector('input[data-channel="b"]'));
+                        this.channels.set("r" /* R */, content.querySelector('input[data-channel="r"]'));
+                        this.channels.set("g" /* G */, content.querySelector('input[data-channel="g"]'));
+                        this.channels.set("b" /* B */, content.querySelector('input[data-channel="b"]'));
+                        this.channels.set("a" /* A */, content.querySelector('input[data-channel="a"]'));
                         this.channels.forEach((input) => {
-                            input.addEventListener("input", () => this.updateColor());
+                            input.addEventListener("input", () => this.updateColor("rgba" /* RGBA */));
+                        });
+                        this.hslContainer = content.querySelector(".colorPickerHsvContainer");
+                        this.hsl.set("hue" /* Hue */, content.querySelector('input[data-coordinate="hue"]'));
+                        this.hsl.set("saturation" /* Saturation */, content.querySelector('input[data-coordinate="saturation"]'));
+                        this.hsl.set("lightness" /* Lightness */, content.querySelector('input[data-coordinate="lightness"]'));
+                        this.hsl.forEach((input) => {
+                            input.addEventListener("input", () => this.updateColor("hsl" /* HSL */));
                         });
-                        this.alphaInput = content.querySelector("input[type=range]");
-                        this.alphaInput.addEventListener("input", () => this.updateColor());
                         this.newColor = content.querySelector(".colorPickerColorNew > span");
                         this.oldColor = content.querySelector(".colorPickerColorOld > span");
                         this.colorTextInput = content.querySelector("input[type=text]");
@@ -138,8 +154,8 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
          *
          * @since 5.5
          */
-        updateColor() {
-            this.setColor(this.getColor());
+        updateColor(source) {
+            this.setColor(this.getColor(source), source);
         }
         /**
          * Updates the current color after the hex input changes its value.
@@ -162,19 +178,24 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
                     return;
                 }
             }
-            this.setColor(color);
+            this.setColor(color, "hex" /* HEX */);
         }
         /**
          * Returns the current RGBA color set via the color and alpha input.
          *
          * @since 5.5
          */
-        getColor() {
+        getColor(source) {
+            const a = parseFloat(this.channels.get("a" /* A */).value);
+            if (source === "hsl" /* HSL */) {
+                const rgb = ColorUtil.hslToRgb(parseInt(this.hsl.get("hue" /* Hue */).value, 10), parseInt(this.hsl.get("saturation" /* Saturation */).value, 10), parseInt(this.hsl.get("lightness" /* Lightness */).value, 10));
+                return Object.assign(Object.assign({}, rgb), { a });
+            }
             return {
-                r: parseInt(this.channels.get(0 /* R */).value, 10),
-                g: parseInt(this.channels.get(1 /* G */).value, 10),
-                b: parseInt(this.channels.get(2 /* B */).value, 10),
-                a: parseInt(this.alphaInput.value, 10),
+                r: parseInt(this.channels.get("r" /* R */).value, 10),
+                g: parseInt(this.channels.get("g" /* G */).value, 10),
+                b: parseInt(this.channels.get("b" /* B */).value, 10),
+                a,
             };
         }
         /**
@@ -190,16 +211,30 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
          *
          * @since 5.5
          */
-        setColor(color) {
+        setColor(color, source) {
             if (typeof color === "string") {
                 color = ColorUtil.stringToRgba(color);
             }
-            this.channels.get(0 /* R */).value = color.r.toString();
-            this.channels.get(1 /* G */).value = color.g.toString();
-            this.channels.get(2 /* B */).value = color.b.toString();
-            this.alphaInput.value = color.a.toString();
+            const { r, g, b, a } = color;
+            const { h, s, l } = ColorUtil.rgbToHsl(r, g, b);
+            if (source !== "hsl" /* HSL */) {
+                this.hsl.get("hue" /* Hue */).value = h.toString();
+                this.hsl.get("saturation" /* Saturation */).value = s.toString();
+                this.hsl.get("lightness" /* Lightness */).value = l.toString();
+            }
+            this.hslContainer.style.setProperty(`--${"hue" /* Hue */}`, `${h}`);
+            this.hslContainer.style.setProperty(`--${"saturation" /* Saturation */}`, `${s}%`);
+            this.hslContainer.style.setProperty(`--${"lightness" /* Lightness */}`, `${l}%`);
+            if (source !== "rgba" /* RGBA */) {
+                this.channels.get("r" /* R */).value = r.toString();
+                this.channels.get("g" /* G */).value = g.toString();
+                this.channels.get("b" /* B */).value = b.toString();
+                this.channels.get("a" /* A */).value = a.toString();
+            }
             this.newColor.style.backgroundColor = ColorUtil.rgbaToString(color);
-            this.colorTextInput.value = ColorUtil.rgbaToHex(color);
+            if (source !== "hex" /* HEX */) {
+                this.colorTextInput.value = ColorUtil.rgbaToHex(color);
+            }
         }
         /**
          * Updates the UI to show the given color as the initial color.
@@ -210,7 +245,7 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
             if (typeof color === "string") {
                 color = ColorUtil.stringToRgba(color);
             }
-            this.setColor(color);
+            this.setColor(color, "setup" /* Setup */);
             this.oldColor.style.backgroundColor = ColorUtil.rgbaToString(color);
         }
         /**
@@ -219,7 +254,7 @@ define(["require", "exports", "tslib", "../../Core", "../Dialog", "../../Dom/Uti
          * @since 5.5
          */
         submitDialog() {
-            const color = this.getColor();
+            const color = this.getColor("rgba" /* RGBA */);
             const colorString = ColorUtil.rgbaToString(color);
             this.oldColor.style.backgroundColor = colorString;
             this.input.value = colorString;
index fde7af87a73a594f43592f6a7f5d1261d6a5eb07..f0e305bba7e3571ca84b10dbb6c1512ca5429a0b 100644 (file)
 }
 
 .colorPickerComparison {
+       --border-radius: 5px;
+
+       display: grid;
+       grid-template-rows: min-content auto auto min-content;
        text-align: center;
+}
 
-       .colorPickerColorNew,
-       .colorPickerColorOld {
-               height: 72px;
+.colorPickerColorNew {
+       border-radius: var(--border-radius) var(--border-radius) 0 0;
+}
 
-               > span {
-                       height: 72px;
-               }
+.colorPickerColorOld {
+       border-radius: 0 0 var(--border-radius) var(--border-radius);
+}
+
+.colorPickerChannels {
+       align-items: center;
+       column-gap: 3px;
+       color: $wcfContentDimmedText;
+       display: flex !important;
+
+       input[type="number"] {
+               padding: 4px;
+               text-align: center;
        }
+}
 
-       .colorPickerColorOld {
-               background-position: 8px 0;
-               border-top-width: 0;
+.colorPickerColorNew,
+.colorPickerColorOld {
+       overflow: hidden;
+
+       > span {
+               height: 100%;
        }
 }
 
+.colorPickerColorOld {
+       background-position: 8px 0;
+       border-top-width: 0;
+}
+
 .colorPickerChannel {
        display: inline-flex;
 }
+
+.colorPickerHslRange,
+.colorPickerHslRange::-webkit-slider-thumb {
+       -webkit-appearance: none;
+       appearance: none;
+}
+
+.colorPickerHslRange {
+       width: 100%;
+
+       //&::-moz-range-track,
+       &::-webkit-slider-runnable-track {
+               background-image: var(--track-image);
+               height: 10px;
+               border-radius: 5px;
+       }
+
+       //&::-moz-range-thumb,
+       &::-webkit-slider-thumb {
+               background-color: hsl(var(--hue), var(--saturation), var(--lightness));
+               border: 4px solid #fff;
+               border-radius: 50%;
+               box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.025), 0 1px 5px rgba(0, 0, 0, 0.25);
+               cursor: pointer;
+               height: 24px;
+               margin-top: -6px;
+               width: 24px;
+       }
+}
+
+.colorPickerHslRange[data-coordinate="hue"] {
+       --track-image: linear-gradient(
+               to right,
+               hsl(0, var(--saturation), var(--lightness)),
+               hsl(10, var(--saturation), var(--lightness)),
+               hsl(20, var(--saturation), var(--lightness)),
+               hsl(30, var(--saturation), var(--lightness)),
+               hsl(40, var(--saturation), var(--lightness)),
+               hsl(50, var(--saturation), var(--lightness)),
+               hsl(60, var(--saturation), var(--lightness)),
+               hsl(70, var(--saturation), var(--lightness)),
+               hsl(80, var(--saturation), var(--lightness)),
+               hsl(90, var(--saturation), var(--lightness)),
+               hsl(100, var(--saturation), var(--lightness)),
+               hsl(110, var(--saturation), var(--lightness)),
+               hsl(120, var(--saturation), var(--lightness)),
+               hsl(130, var(--saturation), var(--lightness)),
+               hsl(140, var(--saturation), var(--lightness)),
+               hsl(150, var(--saturation), var(--lightness)),
+               hsl(160, var(--saturation), var(--lightness)),
+               hsl(170, var(--saturation), var(--lightness)),
+               hsl(180, var(--saturation), var(--lightness)),
+               hsl(190, var(--saturation), var(--lightness)),
+               hsl(200, var(--saturation), var(--lightness)),
+               hsl(210, var(--saturation), var(--lightness)),
+               hsl(220, var(--saturation), var(--lightness)),
+               hsl(230, var(--saturation), var(--lightness)),
+               hsl(240, var(--saturation), var(--lightness)),
+               hsl(250, var(--saturation), var(--lightness)),
+               hsl(260, var(--saturation), var(--lightness)),
+               hsl(270, var(--saturation), var(--lightness)),
+               hsl(280, var(--saturation), var(--lightness)),
+               hsl(290, var(--saturation), var(--lightness)),
+               hsl(300, var(--saturation), var(--lightness)),
+               hsl(310, var(--saturation), var(--lightness)),
+               hsl(320, var(--saturation), var(--lightness)),
+               hsl(330, var(--saturation), var(--lightness)),
+               hsl(340, var(--saturation), var(--lightness)),
+               hsl(350, var(--saturation), var(--lightness)),
+               hsl(359, var(--saturation), var(--lightness))
+       );
+}
+
+.colorPickerHslRange[data-coordinate="saturation"] {
+       --track-image: linear-gradient(
+               to right,
+               hsl(var(--hue), 0%, var(--lightness)) 0%,
+               hsl(var(--hue), 100%, var(--lightness)) 100%
+       );
+}
+
+.colorPickerHslRange[data-coordinate="lightness"] {
+       --track-image: linear-gradient(
+               to right,
+               hsl(var(--hue), var(--saturation), 0%) 0%,
+               hsl(var(--hue), var(--saturation), 50%) 50%,
+               hsl(var(--hue), var(--saturation), 100%) 100%
+       );
+}
+
+.colorPickerValueContainer {
+       column-gap: 20px;
+       display: grid;
+       grid-template-columns: min-content auto;
+       margin-top: 20px;
+}
index 5d7bbf9f62617dddf378214d2b0d9694d73a3805..4971a7e4ca1156044267e4942ffcfa85f9117755 100644 (file)
@@ -4533,11 +4533,13 @@ Dateianhänge:
                <item name="wcf.style.colorPicker.current"><![CDATA[aktuell]]></item>
                <item name="wcf.style.currentStyle"><![CDATA[Aktiver Stil]]></item>
                <item name="wcf.style.colorPicker.button.apply"><![CDATA[Übernehmen]]></item>
-               <item name="wcf.style.colorPicker.alpha"><![CDATA[Deckkraft]]></item>
                <item name="wcf.style.colorPicker.hexAlpha"><![CDATA[Hex-Wert mit Alpha-Kanal]]></item>
                <item name="wcf.style.colorPicker.color"><![CDATA[Farbe]]></item>
                <item name="wcf.style.colorPicker.button.changeColor"><![CDATA[Farbe ändern]]></item>
                <item name="wcf.style.colorPicker.error.invalidColor"><![CDATA[Die angegebene Farbe ist ungültig.]]></item>
+               <item name="wcf.style.colorPicker.hue"><![CDATA[Farbton]]></item>
+               <item name="wcf.style.colorPicker.saturation"><![CDATA[Sättigung]]></item>
+               <item name="wcf.style.colorPicker.lightness"><![CDATA[Helligkeit]]></item>
        </category>
        <category name="wcf.tagging">
                <item name="wcf.tagging.combinedTaggedObjects"><![CDATA[{implode from=$combinedTags item=tag glue=', '}„{$tag->name}“{/implode}]]></item>
@@ -5622,5 +5624,6 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|phra
        <item name="wcf.search.subjectOnly" />
        <item name="wcf.article.search.categories" />
        <item name="wcf.global.button.showSidebarRight" />
+       <item name="wcf.style.colorPicker.alpha" />
 </delete>
 </language>
index 4c82fd7071b1934f9110a3fc47594e6530859968..12ebd25d5841e49a9e3a2a992e8f7da92884cd96 100644 (file)
@@ -4535,11 +4535,13 @@ Attachments:
                <item name="wcf.style.colorPicker.current"><![CDATA[current]]></item>
                <item name="wcf.style.currentStyle"><![CDATA[Current Style]]></item>
                <item name="wcf.style.colorPicker.button.apply"><![CDATA[Apply]]></item>
-               <item name="wcf.style.colorPicker.alpha"><![CDATA[Opacity]]></item>
                <item name="wcf.style.colorPicker.hexAlpha"><![CDATA[Hex Value with Alpha Channel]]></item>
                <item name="wcf.style.colorPicker.color"><![CDATA[Color]]></item>
                <item name="wcf.style.colorPicker.button.changeColor"><![CDATA[Change color]]></item>
                <item name="wcf.style.colorPicker.error.invalidColor"><![CDATA[The entered color is invalid.]]></item>
+               <item name="wcf.style.colorPicker.hue"><![CDATA[Hue]]></item>
+               <item name="wcf.style.colorPicker.saturation"><![CDATA[Saturation]]></item>
+               <item name="wcf.style.colorPicker.lightness"><![CDATA[Lightness]]></item>
        </category>
        <category name="wcf.tagging">
                <item name="wcf.tagging.combinedTaggedObjects"><![CDATA[{implode from=$combinedTags item=tag glue=', '}“{$tag->name}”{/implode}]]></item>
@@ -5624,5 +5626,6 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|phras
        <item name="wcf.search.subjectOnly" />
        <item name="wcf.article.search.categories" />
        <item name="wcf.global.button.showSidebarRight" />
+       <item name="wcf.style.colorPicker.alpha" />
 </delete>
 </language>