@suyouwanggang/p-ui
Version:
`p-ui`是一套使用原生`Web Components`规范开发的跨框架UI组件库,基于`lit-elment`库开发。 [github项目地址](https://github.com/suyouwanggang/p-ui)
477 lines (467 loc) • 19.9 kB
text/typescript
import { css, customElement, html, LitElement, property, query } from 'lit-element';
import { ifDefined } from 'lit-html/directives/if-defined';
import './p-button';
import { rgbToHsv, hslToHsv, parseToHSVA } from './utils/color';
import { HSVaColor } from './utils/hsvacolor';
const Material_colors = ['#f44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B', 'rgba(0,0,0,.65)', 'transparent']
('p-color-panel')
export default class PColorPanel extends LitElement {
({ type: String, reflect: true }) value: string = '#ff0000';
({ type: Number, reflect: true }) typeindex: number = 0;
static COLOR_TYPE: string[] = ['HEXA', 'RGBA', 'HSLA'];
static get styles() {
return css`
:host{
display: block;
width:300px;
}
.color-pane{
padding:.8em;
}
.color-palette{
position:relative;
height:150px;
background:linear-gradient(to top, hsla(0,0%,0%,calc(var(--a))), transparent), linear-gradient(to left, hsla(calc(var(--h)),100%,50%,calc(var(--a))),hsla(0,0%,100%,calc(var(--a)))),linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 ),linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 );
background-position:0 0, 0 0,0 0,5px 5px;
background-size:100% 100%, 100% 100%, 10px 10px, 10px 10px;
user-select: none;
cursor: crosshair;
opacity:1;
transition:opacity .1s;
}
.color-palette:active{
opacity:.99;
}
.color-palette::after{
pointer-events:none;
position:absolute;
content:'';
box-sizing:border-box;
width:10px;
height:10px;
border-radius:50%;
border:2px solid #fff;
left:calc(var(--s) * 1%);
top:calc((100 - var(--v)) * 1%);
transform:translate(-50%,-50%);
}
.color-chooser{
display:flex;
padding:10px 0;
}
.color-show{
display:flex;
position: relative;
width:32px;
height:32px;
background:var(--c);
transition:none;
border-radius:50%;
overflow:hidden;
cursor:pointer;
}
.color-show .icon-file{
width:1em;
height:1em;
margin: auto;
fill: hsl(0, 0%, calc( ((2 - var(--s) / 100) * var(--v) / 200 * var(--a) - 0.6 ) * -999999% ));
opacity: 0;
transition: .3s;
}
.color-show:hover .icon-file{
opacity:1;
}
.color-show input{
position:absolute;
clip:rect(0,0,0,0);
}
.color-show::after{
content:'';
position:absolute;
width:32px;
height:32px;
background:linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 ),linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 );
background-position:0 0,5px 5px;
background-size:10px 10px;
z-index:-1;
}
.color-range{
flex:1;
margin-left:10px;
}
input[type="range"]{
display: block;
pointer-events:all;
width:100%;
-webkit-appearance: none;
outline : 0;
height: 10px;
border-radius:5px;
margin:0;
}
input[type="range"]::-webkit-slider-runnable-track{
display: flex;
align-items: center;
position: relative;
}
input[type="range"]::-webkit-slider-thumb{
-webkit-appearance: none;
position: relative;
width:10px;
height:10px;
transform:scale(1.2);
border-radius: 50%;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
background:#fff;
transition:.2s cubic-bezier(.12, .4, .29, 1.46);
}
input[type="range"]::-moz-range-thumb{
box-sizing:border-box;
pointer-events:none;
position: relative;
width:10px;
height:10px;
transform:scale(1.2);
border-radius: 50%;
border:0;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
background:#fff;
transition:.2s cubic-bezier(.12, .4, .29, 1.46);
}
input[type="range"]::-webkit-slider-thumb:active,
input[type="range"]:focus::-webkit-slider-thumb{
transform:scale(1.5);
}
input[type="range"]::-moz-range-thumb:active,
input[type="range"]:focus::-moz-range-thumb{
transform:scale(1.5);
}
input[type="range"]+input[type="range"]{
margin-top:10px;
}
.color-hue{
background:linear-gradient(to right, red, yellow, lime, cyan, blue, magenta, red)
}
.color-opacity{
background:linear-gradient(to right, hsla(calc(var(--h)),100%,50%,0), hsla(calc(var(--h)),100%,50%,1)),linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 ),linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 );
background-position:0 0,0 0,5px 5px;
background-size:100% 100%,10px 10px,10px 10px;
}
.color-label{
position:absolute;
display:flex;
visibility:hidden;
opacity:0;
left:0;
right:0;
top:0;
bottom:0;
transition: .3s;
}
.color-label input{
flex:1;
margin-right:.8em;
outline:0;
min-width:0;
width: 0;
border-radius:var(--borderRadius,.25em);
border:1px solid #ddd;
padding:0 5px;
line-height:28px;
text-align:center;
-moz-appearance: textfield;
transition:.3s;
}
input[type="number"]::-webkit-inner-spin-button{
display:none;
}
::-moz-focus-inner,::-moz-focus-outer{
border:0;
outline : 0;
}
.color-label input:focus{
border-color:var(--themeColor,#42b983);
}
.color-footer{
display:flex
}
.btn-switch{
position:relative;
border-radius:var(--borderRadius,.25em);
background:none;
border:0;
outline:0;
line-height:30px;
width: 60px;
padding: 0;
color:var(--themeColor,#42b983);
overflow:hidden;
}
.btn-switch::before{
content:'';
position:absolute;
left:0;
top:0;
right:0;
bottom:0;
background:var(--themeBackground,var(--themeColor,#42b983));
opacity:.2;
transition:.3s;
}
.btn-switch:hover::before,.btn-switch:focus::before{
opacity:.3;
}
.color-input{
position:relative;
flex:1;
height:30px;
overflow:hidden;
}
.color-footer[data-type="HEXA"] .color-label:nth-child(1),
.color-footer[data-type="RGBA"] .color-label:nth-child(2),
.color-footer[data-type="HSLA"] .color-label:nth-child(3){
opacity:1;
visibility:inherit;
z-index:2;
}
.color-sign{
padding-top:10px;
display:grid;
grid-template-columns: repeat(auto-fit, minmax(15px, 1fr));
grid-gap: 10px;
}
.color-sign>button{
position:relative;
cursor:pointer;
width:100%;
padding-bottom:0;
padding-top:100%;
border-radius:4px;
border:0;
outline:0;
}
.color-sign>button::before{
content:'';
position:absolute;
left:0;
top:0;
width:100%;
height:100%;
z-index:-1;
background:linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 ),linear-gradient( 45deg, #ddd 25%,transparent 0,transparent 75%,#ddd 0 );
background-position:0 0,5px 5px;
background-size:10px 10px;
border-radius: 4px;
}
.color-sign>button::after{
content:'';
position:absolute;
opacity:.5;
z-index:-2;
left:0;
top:0;
width:100%;
height:100%;
background:inherit;
border-radius:4px;
transition:.3s;
}
.color-sign>button:hover::after,.color-sign>button:focus::after{
transform:translate(2px,2px)
}
`;
}
get color() {
return HSVaColor(...this.$value);
}
get rgbColor() {
return this.color.toRGBA().toString();
}
dispatchChangeEvent() {
this.dispatchEvent(new CustomEvent('change', {
detail: {
value: this.value,
color: this.color
}
}));
}
('#color-palette')
palette: HTMLElement;
private _colorSelectStart = false;
private _moveColorPanel(ev: MouseEvent) {
const { left: x, top: y, width: w, height: h } = this.palette.getBoundingClientRect();
const value = [...this.$value];
const _x = Math.min(Math.max(0, (ev.clientX - x) / w * 100), 100);
const _y = Math.min(Math.max(0, (ev.clientY - y) / h * 100), 100);
value[1] = _x;
value[2] = 100 - _y;
this.value = `hsva(${value[0]}, ${value[1]}%, ${value[2]}%, ${value[3]})`;
}
private _colorChoose(ev: MouseEvent) {
if (ev.type === 'mousedown') {
this._colorSelectStart = true;
this._moveColorPanel(ev);
}
if (this._colorSelectStart && ev.type === 'mousemove') {
this._moveColorPanel(ev);
}
if (ev.type === 'mouseup') {
this._moveColorPanel(ev);
this._colorSelectStart = false;
this.dispatchChangeEvent();
}
}
render() {
return html`
<div class="color-pane" id="color-pane" >
<div class="color-palette" id="color-palette" @mousedown=${this._colorChoose} @mousemove=${this._colorChoose} @mouseup=${this._colorChoose} ></div>
<div class="color-chooser">
<a class="color-show" id="copy-btn">
<svg class="icon-file" viewBox="0 0 1024 1024">
<path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32z"></path>
<path d="M704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
</svg><input type="text" id="copyOnInfo">
</a>
<div class="color-range">
<input class="color-hue" value="0" min="0" max="360" type="range" id="range-hue" @input=${this._range_hueHander}>
<input class="color-opacity" value="1" min="0" max="1" step="0.01" type="range" id="range-opacity" @input=${this._rangeOpacityHander} >
</div>
</div>
<div class="color-footer" data-type="${PColorPanel.COLOR_TYPE[this.typeindex]}">
<div class="color-input">
<div class="color-label" id="color-hexa">
<input spellcheck="false" @change=${this._hexaChangeHander} @mouseleave=${this._hexaChangeHander} id='color_hexa_input' />
</div>
<div class="color-label" id="color-rgba" @change=${this._rgbaChangeHander} >
<input type="number" min="0" max="255" spellcheck="false" />
<input type="number" min="0" max="255" spellcheck="false" />
<input type="number" min="0" max="255" spellcheck="false" />
<input type="number" min="0" max="1" step="0.01" spellcheck="false" />
</div>
<div class="color-label" id="color-hlsa" @change=${this._hlsaChangeHander} >
<input type="number" min="0" max="360" maxlength='3' spellcheck="false" />
<input type="number" min="0" max="100" maxlength=3 spellcheck="false" />
<input type="number" min="0" max="100" maxlength=3 spellcheck="false" />
<input type="number" min="0" max="1" step="0.01" spellcheck="false" />
</div>
</div>
<p-button class="btn-switch" id="btn-switch" @click=${this._switch_Hander} type="flat">${PColorPanel.COLOR_TYPE[this.typeindex]}</p-button>
</div>
<div class="color-sign" id="colors" @click=${this._colorsPick}>
${Material_colors.map((el: string) => html`<button style="background-color:${el};" data-color='${el}' ></button>`)}
</div>
</div>`;
}
private _rangeOpacityHander(ev: InputEvent) {
const rangeOpcity: HTMLInputElement = ev.target as HTMLInputElement;
const value = [...this.$value];
value[3] = Number(rangeOpcity.value);
this.value = `hsva(${value[0]}, ${value[1]}%, ${value[2]}%, ${value[3]})`;
}
private _range_hueHander(ev: InputEvent) {
const rangeHue: HTMLInputElement = ev.target as HTMLInputElement;
const value = [...this.$value];
value[0] = Number(rangeHue.value);
this.value = `hsva(${value[0]}, ${value[1]}%, ${value[2]}%, ${value[3]})`;
}
private _switch_Hander(ev: Event) {
this.typeindex++;
this.typeindex %= 3;
}
get copyValue() {
const color = this.color;
if (this.typeindex === 0) {
return color.toHEXA().toString();
} else if (this.typeindex === 1) {
return color.toRGBA().toString();
} else {
return color.toHSLA().toString();
}
}
private _hexaChangeHander(event: Event) {
const el: HTMLInputElement = event.target as HTMLInputElement;
this.value = el.value;
this.dispatchChangeEvent();
}
private _rgbaChangeHander(event: Event) {
const rgbaDIV = this.renderRoot.querySelector('#color-rgba');
const input = event.target as HTMLInputElement;
const index = [...rgbaDIV.children as any].indexOf(input);
const value = HSVaColor(...this.$value).toRGBA();
value[index] = Number(input.value);
this.value = `rgba(${value[0]}, ${value[1]}, ${value[2]}, ${value[3]})`;
}
private _hlsaChangeHander(event: Event) {
const hlsaDIV = this.renderRoot.querySelector('#color-hlsa');
const input = event.target as HTMLInputElement;
const index = [...hlsaDIV.children as any].indexOf(input);
const value = HSVaColor(...this.$value).toHSLA();
value[index] = Number(input.value);
this.value = `hsla(${value[0]}, ${value[1]}%, ${value[2]}%, ${value[3]})`;
}
private _colorsPick(ev: Event) {
let item = ev.target as HTMLElement;
item = item.closest('button');
if (item) {
this.value = item.getAttribute('data-color');
}
}
get rgbaInputs(): HTMLInputElement[] {
return [...this.renderRoot.querySelectorAll('#color-rgba input') as any];
}
get hlsaInputs(): HTMLInputElement[] {
return [...this.renderRoot.querySelectorAll('#color-hlsa input') as any];
}
('#range-hue')
private rangeHueEL: HTMLInputElement;
('#range-opacity')
private rangeOpcity: HTMLInputElement;
('#copyOnInfo')
private copyInfoInput: HTMLInputElement;
('#color_hexa_input')
private color_hexa_input: HTMLInputElement;
private $value: any[];
private _setValueAsyn() {
this.$value = parseToHSVA(this.value).values;
const [h, s, v, a = 1] = this.$value;
const pane = this.shadowRoot!.getElementById('color-pane');
if (pane) {
pane.style.setProperty('--h', String(h));
pane.style.setProperty('--s', String(s));
pane.style.setProperty('--v', String(v));
pane.style.setProperty('--a', String(a));
pane.style.setProperty('--c', this.copyValue);
this.rangeHueEL.value = String(h);
this.rangeOpcity.value = a.toFixed(2);
this.copyInfoInput.value = this.copyValue;
const rgbaInputs = this.rgbaInputs;
const color = HSVaColor(...this.$value);
this.color_hexa_input.value = color.toHEXA().toString();
const rgba = color.toRGBA();
rgbaInputs[0].value = rgba[0].toFixed(0);
rgbaInputs[1].value = rgba[1].toFixed(0);
rgbaInputs[2].value = rgba[2].toFixed(0);
rgbaInputs[3].value = rgba[3].toFixed(2);
const hlsaInputs = this.hlsaInputs;
const hlsa = color.toHSLA();
hlsaInputs[0].value = hlsa[0].toFixed(0);
hlsaInputs[1].value = hlsa[1].toFixed(0);
hlsaInputs[2].value = hlsa[2].toFixed(0);
hlsaInputs[3].value = hlsa[3].toFixed(2);
}
}
update(_changedProperties: Map<string | number | symbol, unknown>) {
super.update(_changedProperties);
if (_changedProperties.has('value') && this.isConnected) {
const result = parseToHSVA(this.value);
if (result.values == null) {
this.value = _changedProperties.get('value') as string;
} else {
this._setValueAsyn();
}
}
}
firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
super.firstUpdated(_changedProperties);
}
}