@alegendstale/holly-components
Version:
Reusable UI components created using lit
448 lines (432 loc) • 15.3 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LitElement, html, nothing, css } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { repeat } from 'lit/directives/repeat.js';
import { defaultSettings, Direction, PaletteError, parseColors, Status } from './color-palette-utils.js';
import { ColorPaletteItem } from './item/color-palette-item.js';
import { copyToClipboard } from '../../utils/basicUtils.js';
import { EventEmitter } from '../../utils/EventEmitter.js';
import { CanvasGradient } from '../canvas/canvas-gradient.js';
import { classMap } from 'lit/directives/class-map.js';
import { ColorPaletteItemEdit } from './item/color-palette-item-edit.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { condCustomElement } from '../../decorators/condCustomElement.js';
let ColorPalette = class ColorPalette extends LitElement {
constructor() {
super(...arguments);
this._editMode = false;
this.colors = [];
// Settings
this.height = defaultSettings.height;
this.width = defaultSettings.width;
this.direction = defaultSettings.direction;
this.gradient = false;
this.preventHover = false;
this.hideText = false;
this.override = false;
this.aliases = [];
this.maxWidth = 0;
this.status = Status.VALID;
this.pulse = false;
this.emitter = new EventEmitter();
this.pluginSettings = defaultSettings;
this.createPalette = () => {
try {
if (this.status === Status.VALID) {
if (this.gradient)
return this.createGradient();
else
return this.createColors();
}
else {
// Throw error & create Invalid Palette
throw new PaletteError(this.status);
}
}
catch (err) {
if (!(err instanceof PaletteError))
return nothing;
this.emitter.emit('notice', err.message);
return this.createInvalidPalette(err.status, err.message);
}
};
}
set editMode(val) {
this._editMode = val;
this.emitter.emit('editMode', val);
}
get editMode() {
return this._editMode;
}
/**
* Helper accessor for settings
*/
set settings({ height, width, direction, gradient, preventHover, override, aliases }) {
this.height = height;
this.width = width;
this.direction = direction;
this.gradient = gradient;
this.preventHover = preventHover;
this.override = override;
this.aliases = aliases;
}
get settings() {
return {
height: this.height,
width: this.width,
direction: this.direction,
gradient: this.gradient,
preventHover: this.preventHover,
hideText: this.hideText,
override: this.override,
aliases: this.aliases,
};
}
connectedCallback() {
super.connectedCallback();
// Pulse the Invalid Palette to show its location
if (this.pluginSettings.errorPulse) {
this.pulse = true;
setTimeout(() => {
this.pulse = false;
}, this.pluginSettings.noticeDuration);
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.emitter.clear();
}
willUpdate(_changedProperties) {
super.willUpdate(_changedProperties);
this.setStatusAndColors();
}
/**
* Sets the current status and parses colors
*/
setStatusAndColors() {
const isSettingsValid = () => {
return (!isNaN(this.height)
&& !isNaN(this.width)
&& Object.values(Direction).includes(this.direction)
&& this.gradient != null
&& this.preventHover != null
&& this.hideText != null
&& this.override != null
&& (this.aliases instanceof Array
|| typeof this.aliases === 'undefined'));
};
const colorsResult = parseColors(this.colors, this.override);
if (!isSettingsValid()) {
this.status = Status.INVALID_SETTINGS;
}
// Set status to invalid if parsedColors returns a status, or if parsedColors returns an empty array
if (typeof colorsResult === 'string' || (typeof colorsResult === 'object' && colorsResult.length === 0)) {
this.status = this.status === Status.INVALID_SETTINGS ? Status.INVALID_COLORS_AND_SETTINGS : Status.INVALID_COLORS;
}
if (typeof colorsResult === 'object') {
this.colors = colorsResult;
}
if (this.colors.length <= 1 && this.gradient)
this.status = Status.INVALID_GRADIENT;
}
render() {
const paletteStyles = {
// Set default corner style
'--palette-corners': this.pluginSettings.corners ? '5px' : '0px',
'--palette-height': `${this.height}px`,
'--palette-width': `${this.getPaletteWidth()}px`,
'--palette-column-flex-basis': `${(this.height / this.colors.length / 2)}px`
};
const invalidStyles = {
animation: this.pulse ? `pulse ${this.pluginSettings.noticeDuration / 1000 / 2}s infinite` : ''
};
const paletteClasses = {
"palette-scroll": this.width > defaultSettings.width,
};
return html `
<div
id="palette"
class=${classMap(paletteClasses)}
style=${this.status === Status.VALID ? styleMap(paletteStyles) : styleMap(invalidStyles)}
>
${this.createPalette()}
</div>
`;
}
/**
* @returns `user` OR `auto` width based on which is more appropriate
*/
getPaletteWidth() {
// Automatically set width if offset is less than settings width
if (this.maxWidth < this.settings.width && this.maxWidth > 0)
return this.maxWidth;
// Set user-set width
else
return this.settings.width;
}
createColors() {
return html `
${repeat(this.colors, (color, index) => {
let itemRef = (item) => {
if (!(item instanceof ColorPaletteItem))
return;
item.emitter.clear();
item.emitter.on('click', async (e) => await copyToClipboard(color.toUpperCase(), this.pluginSettings.copyFormat));
};
let itemEditRef = (item) => {
if (!(item instanceof ColorPaletteItemEdit))
return;
item.emitter.clear();
item.emitter.on('click', async (e) => await copyToClipboard(color.toUpperCase(), this.pluginSettings.copyFormat));
item.emitter.on('trash', (e) => {
e.stopPropagation();
const deletedIndex = this.colors.indexOf(color);
this.colors = this.colors.filter((_, index) => index !== deletedIndex);
this.aliases = this.aliases.filter((_, index) => index !== deletedIndex);
this.emitter.emit('changed', this.colors, this.settings);
});
item.emitter.on('alias', (alias) => {
// Get the index of the alias relative to the PaletteItem color
const aliasIndex = this.colors.findIndex((val) => val === color);
for (let i = 0; i < aliasIndex; i++) {
// Set empty strings to empty indexes
if (!this.aliases[i])
this.aliases[i] = '';
}
// Set modified alias index
this.aliases[aliasIndex] = alias;
this.emitter.emit('changed', this.colors, this.settings);
});
};
return !this.editMode
? html `
<color-palette-item
color=${color}
alias=${this.aliases?.[index] || ''}
aliasMode=${this.pluginSettings.aliasMode}
direction=${this.direction}
?editMode=${this.editMode}
height=${this.height}
?preventHover=${this.preventHover}
?hideText=${this.hideText}
colorCount=${this.colors.length}
${ref(itemRef)}
>
</color-palette-item>
`
: html `
<color-palette-item-edit
draggable=${true}
color=${color}
alias=${this.aliases?.[index] || ''}
aliasMode=${this.pluginSettings.aliasMode}
direction=${this.direction}
?editMode=${this.editMode}
height=${this.height}
?preventHover=${this.preventHover}
?hideText=${this.hideText}
colorCount=${this.colors.length}
?stabilizeWhileEditing=${this.pluginSettings.stabilizeWhileEditing}
${ref(itemEditRef)}
>
</color-palette-item-edit>
`;
})}
`;
}
createGradient() {
let tooltipRef = createRef();
let canvasRef = (canvas) => {
if (!(canvas instanceof CanvasGradient))
return;
canvas.emitter.clear();
canvas.emitter.on('click', async (color) => await copyToClipboard(color.toUpperCase(), this.pluginSettings.copyFormat));
canvas.emitter.on('move', (pos) => {
if (!tooltipRef.value)
return;
let tooltip = tooltipRef.value;
// Set tooltip text
tooltip.text = canvas.getCanvasHex(pos.x, pos.y).toUpperCase();
// Set tooltip position
tooltip.setClampedPosition(pos, this.getBoundingClientRect());
});
};
return html `
<tool-tip
id="tooltip"
trigger="hover"
?display=${!this.preventHover}
${ref(tooltipRef)}
>
<canvas-gradient
.colors=${this.colors}
height=${this.height}
width=${this.getPaletteWidth()}
direction=${this.direction}
id="canvas"
${ref(canvasRef)}
>
</canvas-gradient>
</tool-tip>
`;
}
/**
* Create invalid palette based on palette status
* @param type Palette status type
*/
createInvalidPalette(type, message = '') {
let defaultMessage = 'Invalid palette';
switch (type) {
case Status.INVALID_COLORS:
defaultMessage = 'Colors are defined incorrectly';
break;
case Status.INVALID_SETTINGS:
defaultMessage = 'Issues parsing settings';
break;
case Status.INVALID_COLORS_AND_SETTINGS:
defaultMessage = 'Colors and settings are defined incorrectly';
break;
case Status.INVALID_GRADIENT:
defaultMessage = 'Gradients require more than 1 color to display';
break;
}
this.emitter.emit('notice', `Palette:\n${message ? message : defaultMessage}`);
return html `
<section id="invalid">
<span>${type}</span>
</section>
`;
}
};
ColorPalette.styles = [
css `
:host {
display: block;
width: fit-content;
}
:host([direction='row']) #palette {
flex-direction: column;
}
/* Edit Mode */
:host([editMode]) #palette {
overflow-x: auto;
}
// Invalid Palette
:host(:not([status='Valid'])) #palette {
}
/* Palette Container */
#container {
cursor: pointer;
position: relative;
background-color: #000;
contain: paint;
}
#container.palette-scroll {
overflow-x: auto;
}
/* Palette */
#palette {
/* fallback vars */
--palette-height: 150px;
--palette-width: 700px;
--palette-background-color: transparent;
--palette-color: #000;
--palette-column-flex-basis: 80px;
--palette-corners: 5px;
display: flex;
flex-direction: row;
border-radius: var(--palette-corners);
overflow: hidden;
cursor: pointer;
height: var(--palette-height);
width: var(--palette-width);
position: relative;
scrollbar-width: thin;
/* Drag & Drop */
& > .is-dragging {
opacity: 0;
}
& > #invalid {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
height: var(--palette-height);
width: var(--palette-width);
background-color: #000000c0;
border-radius: var(--palette-corners);
& > span {
display: flex;
justify-content: center;
align-items: center;
color: white;
background-color: #000;
width: 100%;
height: 30%;
text-align: center;
user-select: none;
}
}
}
pulse {
50% {
background-color: #FFFF0080;
}
}
`,
];
__decorate([
query('#palette')
], ColorPalette.prototype, "palette", void 0);
__decorate([
property({ type: Boolean })
], ColorPalette.prototype, "editMode", null);
__decorate([
property({ type: Array, reflect: true })
], ColorPalette.prototype, "colors", void 0);
__decorate([
property({ type: Number, reflect: true })
], ColorPalette.prototype, "height", void 0);
__decorate([
property({ type: Number, reflect: true })
], ColorPalette.prototype, "width", void 0);
__decorate([
property({ type: String, reflect: true })
], ColorPalette.prototype, "direction", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], ColorPalette.prototype, "gradient", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], ColorPalette.prototype, "preventHover", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], ColorPalette.prototype, "hideText", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], ColorPalette.prototype, "override", void 0);
__decorate([
property({ type: Array, reflect: true })
], ColorPalette.prototype, "aliases", void 0);
__decorate([
property({ type: Number })
], ColorPalette.prototype, "maxWidth", void 0);
__decorate([
state()
], ColorPalette.prototype, "status", void 0);
__decorate([
state()
], ColorPalette.prototype, "pulse", void 0);
__decorate([
property({ type: Object })
], ColorPalette.prototype, "pluginSettings", void 0);
ColorPalette = __decorate([
condCustomElement('color-palette')
], ColorPalette);
export { ColorPalette };