@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
233 lines (232 loc) • 10.9 kB
JavaScript
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */
import { c as customElement } from "../../chunks/runtime.js";
import { keyed } from "lit/directives/keyed.js";
import Color from "color";
import { css, html } from "lit";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { createRef, ref } from "lit/directives/ref.js";
import { n as normalizeHex, b as hexify, i as isValidHex, s as isLonghandHex, u as isShorthandHex, v as rgbToHex, o as opacityToAlpha, q as alphaToOpacity, O as OPACITY_LIMITS, w as hexChar } from "../../chunks/utils.js";
import { u as useSetFocus } from "../../chunks/useSetFocus.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{--calcite-input-text-background-color: var(--calcite-color-picker-input-background-color);--calcite-input-text-border-color: var(--calcite-color-picker-input-border-color);--calcite-input-text-text-color: var(--calcite-color-picker-input-text-color);--calcite-input-prefix-text-color: var(--calcite-color-picker-input-prefix-text-color)}.opacity-input{--calcite-input-number-background-color: var(--calcite-color-picker-input-background-color);--calcite-input-number-border-color: var(--calcite-color-picker-input-border-color);--calcite-input-number-text-color: var(--calcite-color-picker-input-text-color);--calcite-input-suffix-text-color: var(--calcite-color-picker-input-suffix-text-color)}.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.hexInputRef = createRef();
this.opacityInputRef = createRef();
this.focusSetter = useSetFocus()(this);
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(options) {
return this.focusSetter(() => this.hexInputRef.value, options);
}
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.hexInputRef.value;
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.opacityInputRef.value;
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.hexInputRef.value.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.opacityInputRef.value;
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) {
const focusTarget = event.type === "calciteInternalInputTextFocus" ? this.hexInputRef : this.opacityInputRef;
focusTarget.value.selectText();
}
onHexInputInput() {
const hexInputValue = `#${this.hexInputRef.value.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, hexInputRef, internalColor, value } = this;
const { key } = event;
const composedPath = event.composedPath();
if (key === "Tab" && isShorthandHex(value, this.alphaChannel) || key === "Enter") {
if (composedPath.includes(hexInputRef.value)) {
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(hexInputRef.value) ? "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.hexInputRef.value.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;
}
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.hexInputRef)}></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.opacityInputRef)}></calcite-input-number>`) : null}</div>`;
}
}
customElement("calcite-color-picker-hex-input", ColorPickerHexInput);
export {
ColorPickerHexInput
};