UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

244 lines (243 loc) • 10.4 kB
/*! 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} @keydown=${this.onInputKeyDown} @paste=${this.onHexInputPaste} @calciteInputTextChange=${this.onHexInputChange} @calciteInputTextInput=${this.onHexInputInput} @calciteInternalInputTextBlur=${this.onHexInputBlur} @calciteInternalInputTextFocus=${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} @keydown=${this.onInputKeyDown} @calciteInputNumberInput=${this.onOpacityInputInput} @calciteInternalInputNumberBlur=${this.onOpacityInputBlur} @calciteInternalInputNumberFocus=${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 };