@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
244 lines (243 loc) • 10.4 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { c as customElement } from "../../chunks/runtime.js";
import { ref } from "lit-html/directives/ref.js";
import { keyed } from "lit-html/directives/keyed.js";
import Color from "color";
import { html } from "lit";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { n as normalizeHex, h as hexify, i as isValidHex, a as isLonghandHex, b as isShorthandHex, r as rgbToHex, o as opacityToAlpha, c as alphaToOpacity, O as OPACITY_LIMITS, d as hexChar } from "../../chunks/utils4.js";
import { b as focusElement } from "../../chunks/dom.js";
import { c as componentFocusable } from "../../chunks/component.js";
import { css } from "@lit/reactive-element/css-tag.js";
const CSS = {
container: "container",
hexInput: "hex-input",
opacityInput: "opacity-input"
};
const styles = css`:host{display:block}.container{display:flex;inline-size:100%;flex-wrap:nowrap;align-items:center}.hex-input{flex-grow:1;text-transform:uppercase}.opacity-input{inline-size:100px;margin-inline-start:-1px}:host([hidden]){display:none}[hidden]{display:none}`;
const DEFAULT_COLOR = Color();
class ColorPickerHexInput extends LitElement {
constructor() {
super(...arguments);
this.internalColor = DEFAULT_COLOR;
this.alphaChannel = false;
this.allowEmpty = false;
this.hexLabel = "Hex";
this.scale = "m";
this.value = normalizeHex(hexify(DEFAULT_COLOR, this.alphaChannel), this.alphaChannel, true);
this.calciteColorPickerHexInputChange = createEvent({ cancelable: false });
}
static {
this.properties = { internalColor: [16, {}, { state: true }], alphaChannel: [5, {}, { type: Boolean }], allowEmpty: [5, {}, { type: Boolean }], hexLabel: 1, messages: [0, {}, { attribute: false }], numberingSystem: 1, scale: [3, {}, { reflect: true }], value: [3, {}, { reflect: true }] };
}
static {
this.styles = styles;
}
async setFocus() {
await componentFocusable(this);
return focusElement(this.hexInputNode);
}
connectedCallback() {
super.connectedCallback();
this.previousNonNullValue = this.value;
const { allowEmpty, alphaChannel, value } = this;
if (value) {
const normalized = normalizeHex(value, alphaChannel);
if (isValidHex(normalized, alphaChannel)) {
this.internalSetValue(normalized, normalized, false);
}
return;
}
if (allowEmpty) {
this.internalSetValue(void 0, void 0, false);
}
}
willUpdate(changes) {
if (changes.has("value") && (this.hasUpdated || this.value !== normalizeHex(hexify(DEFAULT_COLOR, this.alphaChannel), this.alphaChannel, true))) {
this.internalSetValue(this.value, changes.get("value"), false);
}
}
onHexInputBlur() {
const node = this.hexInputNode;
const inputValue = node.value;
const hex = `#${inputValue}`;
const { allowEmpty, internalColor } = this;
const willClearValue = allowEmpty && !inputValue;
const isLonghand = isLonghandHex(hex);
const anyShorthand = isShorthandHex(hex, true) || isShorthandHex(hex, false);
if (anyShorthand) {
this.onHexInputChange();
}
if (willClearValue || isValidHex(hex) && isLonghand) {
return;
}
node.value = allowEmpty && !internalColor ? "" : this.formatHexForInternalInput(rgbToHex(
// always display hex input in RRGGBB format
internalColor.object()
));
}
onOpacityInputBlur() {
const node = this.opacityInputNode;
const inputValue = node.value;
const { allowEmpty, internalColor } = this;
const willClearValue = allowEmpty && !inputValue;
if (willClearValue) {
return;
}
node.value = allowEmpty && !internalColor ? "" : this.formatOpacityForInternalInput(internalColor);
}
onOpacityInputInput() {
this.onOpacityInputChange();
}
onHexInputChange() {
const nodeValue = this.hexInputNode.value;
let value = nodeValue;
if (value) {
const normalized = normalizeHex(value, false);
const preserveExistingAlpha = isValidHex(normalized) && this.alphaChannel;
if (preserveExistingAlpha && this.internalColor) {
const alphaHex = normalizeHex(this.internalColor.hexa(), true).slice(-2);
value = `${normalized + alphaHex}`;
}
}
this.internalSetValue(value, this.value);
}
onOpacityInputChange() {
const node = this.opacityInputNode;
let value;
if (!node.value) {
value = node.value;
} else {
const alpha = opacityToAlpha(Number(node.value));
value = this.internalColor?.alpha(alpha).hexa();
}
this.internalSetValue(value, this.value);
}
onInputFocus(event) {
if (event.type === "calciteInternalInputTextFocus") {
this.hexInputNode.selectText();
} else {
this.opacityInputNode.selectText();
}
}
onHexInputInput() {
const hexInputValue = `#${this.hexInputNode.value}`;
const oldValue = this.value;
if (isValidHex(hexInputValue, this.alphaChannel) && isLonghandHex(hexInputValue, this.alphaChannel)) {
this.internalSetValue(hexInputValue, oldValue);
}
}
onInputKeyDown(event) {
const { altKey, ctrlKey, metaKey, shiftKey } = event;
const { alphaChannel, hexInputNode, internalColor, value } = this;
const { key } = event;
const composedPath = event.composedPath();
if (key === "Tab" && isShorthandHex(value, this.alphaChannel) || key === "Enter") {
if (composedPath.includes(hexInputNode)) {
this.onHexInputChange();
} else {
this.onOpacityInputChange();
}
if (key === "Enter") {
event.preventDefault();
}
return;
}
const isNudgeKey = key === "ArrowDown" || key === "ArrowUp";
const oldValue = this.value;
if (isNudgeKey) {
if (!value) {
this.internalSetValue(this.previousNonNullValue, oldValue);
event.preventDefault();
return;
}
const direction = key === "ArrowUp" ? 1 : -1;
const bump = shiftKey ? 10 : 1;
this.internalSetValue(hexify(this.nudgeRGBChannels(internalColor, bump * direction, composedPath.includes(hexInputNode) ? "rgb" : "a"), alphaChannel), oldValue);
event.preventDefault();
return;
}
const withModifiers = altKey || ctrlKey || metaKey;
const singleChar = key.length === 1;
const validHexChar = hexChar.test(key);
if (singleChar && !withModifiers && !validHexChar) {
event.preventDefault();
}
}
onHexInputPaste(event) {
const hex = event.clipboardData.getData("text");
if (isValidHex(hex, this.alphaChannel) && isLonghandHex(hex, this.alphaChannel)) {
event.preventDefault();
this.hexInputNode.value = hex.slice(1);
this.internalSetValue(hex, this.value);
}
}
internalSetValue(value, oldValue, emit = true) {
if (value) {
const { alphaChannel } = this;
const normalized = normalizeHex(value, alphaChannel, alphaChannel);
if (isValidHex(normalized, alphaChannel)) {
const { internalColor: currentColor } = this;
const nextColor = Color(normalized);
const normalizedLonghand = normalizeHex(hexify(nextColor, alphaChannel), alphaChannel);
const changed = !currentColor || normalizedLonghand !== normalizeHex(hexify(currentColor, alphaChannel), alphaChannel);
this.internalColor = nextColor;
this.previousNonNullValue = normalizedLonghand;
this.value = normalizedLonghand;
if (changed && emit) {
this.calciteColorPickerHexInputChange.emit();
}
return;
}
} else if (this.allowEmpty) {
this.internalColor = void 0;
this.value = void 0;
if (emit) {
this.calciteColorPickerHexInputChange.emit();
}
return;
}
this.value = oldValue;
}
storeHexInputRef(node) {
this.hexInputNode = node;
}
storeOpacityInputRef(node) {
this.opacityInputNode = node;
}
formatHexForInternalInput(hex) {
return hex ? hex.replace("#", "").slice(0, 6) : "";
}
formatOpacityForInternalInput(color) {
return color ? `${alphaToOpacity(color.alpha())}` : "";
}
nudgeRGBChannels(color, amount, context) {
let nudgedChannels;
const channels = color.array();
const rgbChannels = channels.slice(0, 3);
if (context === "rgb") {
const nudgedRGBChannels = rgbChannels.map((channel) => channel + amount);
nudgedChannels = [
...nudgedRGBChannels,
this.alphaChannel ? channels[3] : void 0
];
} else {
const nudgedAlpha = opacityToAlpha(alphaToOpacity(color.alpha()) + amount);
nudgedChannels = [...rgbChannels, nudgedAlpha];
}
return Color(nudgedChannels);
}
render() {
const { alphaChannel, hexLabel, internalColor, messages, scale, value } = this;
const hexInputValue = this.formatHexForInternalInput(value);
const opacityInputValue = this.formatOpacityForInternalInput(internalColor);
const inputScale = scale === "l" ? "m" : "s";
return html`<div class=${safeClassMap(CSS.container)}><calcite-input-text class=${safeClassMap(CSS.hexInput)} .label=${messages?.hex || hexLabel} .maxLength=${this.alphaChannel ? 8 : 6} =${this.onInputKeyDown} =${this.onHexInputPaste} =${this.onHexInputChange} =${this.onHexInputInput} =${this.onHexInputBlur} =${this.onInputFocus} prefix-text=# .scale=${inputScale} .value=${hexInputValue} ${ref(this.storeHexInputRef)}></calcite-input-text>${alphaChannel ? keyed("opacity-input", html`<calcite-input-number class=${safeClassMap(CSS.opacityInput)} .label=${messages?.opacity} .max=${OPACITY_LIMITS.max} max-length=3 .min=${OPACITY_LIMITS.min} number-button-type=none .numberingSystem=${this.numberingSystem} =${this.onInputKeyDown} =${this.onOpacityInputInput} =${this.onOpacityInputBlur} =${this.onInputFocus} .scale=${inputScale} suffix-text=% .value=${opacityInputValue} ${ref(this.storeOpacityInputRef)}></calcite-input-number>`) : null}</div>`;
}
}
customElement("calcite-color-picker-hex-input", ColorPickerHexInput);
export {
ColorPickerHexInput
};