first
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<theme-input
|
||||
v-if="isGlobal"
|
||||
:val="value"
|
||||
@change="onChange"
|
||||
></theme-input>
|
||||
<el-select
|
||||
size="medium"
|
||||
v-if="!isGlobal"
|
||||
v-model="value"
|
||||
class="select"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Mixin from './mixin';
|
||||
import Input from './input';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
components: {
|
||||
themeInput: Input
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
const golbalV = this.golbalValue.border;
|
||||
if (golbalV) {
|
||||
Object.keys(golbalV).forEach((font) => {
|
||||
if (font.includes('border-radius')) {
|
||||
const size = golbalV[font];
|
||||
this.options.push({
|
||||
value: size.key,
|
||||
label: getStyleDisplayName(size)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
158
examples/components/theme-configurator/editor/boxShadow.vue
Normal file
158
examples/components/theme-configurator/editor/boxShadow.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
class="plus-button"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-plus"
|
||||
@click.stop="onAddShadow"
|
||||
>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="config-content" v-for="(each, key) in valueArr" :key="key">
|
||||
<div class="content-10">
|
||||
<color-picker
|
||||
size="mini"
|
||||
class="colorPicker"
|
||||
v-model="each.color"
|
||||
@change="val => onInputChange(val, key, 'color')"
|
||||
show-alpha
|
||||
></color-picker>
|
||||
<span class="content-tip">Color</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.offsetX"
|
||||
@change="val => onInputChange(Number(val), key, 'offsetX')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">X-px</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.offsetY"
|
||||
@change="val => onInputChange(Number(val), key, 'offsetY')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">Y-px</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.spreadRadius"
|
||||
@change="val => onInputChange(Number(val), key, 'spreadRadius')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">Spread</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.blurRadius"
|
||||
@change="val => onInputChange(Number(val), key, 'blurRadius')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">Blur</span>
|
||||
</div>
|
||||
<div class="content-10">
|
||||
<el-button
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-minus"
|
||||
@click.stop="val => onMinusShadow(key)"
|
||||
></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plus-button {
|
||||
position: absolute;
|
||||
left: 90%;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.colorPicker {
|
||||
margin-left: 0;
|
||||
}
|
||||
.content-20 .el-input__suffix-inner span{
|
||||
line-height: 28px;
|
||||
}
|
||||
.content-20 {
|
||||
padding: 0 3px;
|
||||
}
|
||||
.content-10 {
|
||||
vertical-align: top;
|
||||
}
|
||||
.content-tip {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.config-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
/* Element buton style override */
|
||||
.el-button--mini.is-round {
|
||||
padding: 3px 3px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import Mixin from './mixin';
|
||||
import Input from './input';
|
||||
import { parse as parseShaodw, stringify as stringifyShaodw } from '../utils/boxShadow.js';
|
||||
import ColorPicker from './color-picker';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColorPicker,
|
||||
themeInput: Input
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueArr: []
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
methods: {
|
||||
onAddShadow() {
|
||||
this.valueArr.push({
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
spreadRadius: 0,
|
||||
blurRadius: 0,
|
||||
color: 'rgba(0,0,0,0)',
|
||||
inset: false
|
||||
});
|
||||
},
|
||||
onMinusShadow(index) {
|
||||
this.valueArr.splice(index, 1);
|
||||
this.onShadowChange();
|
||||
},
|
||||
onInputChange(e, index, key) {
|
||||
const arr = this.valueArr[index];
|
||||
arr[key] = e;
|
||||
this.valueArr.splice(index, 1, arr);
|
||||
this.onShadowChange();
|
||||
},
|
||||
onShadowChange() {
|
||||
this.onChange(
|
||||
stringifyShaodw(this.valueArr)
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.valueArr = parseShaodw(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,8 @@
|
||||
import ColorPicker from './src/main';
|
||||
|
||||
/* istanbul ignore next */
|
||||
ColorPicker.install = function(Vue) {
|
||||
Vue.component(ColorPicker.name, ColorPicker);
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
@@ -0,0 +1,316 @@
|
||||
const hsv2hsl = function(hue, sat, val) {
|
||||
return [
|
||||
hue,
|
||||
(sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) || 0,
|
||||
hue / 2
|
||||
];
|
||||
};
|
||||
|
||||
// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
||||
// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
||||
const isOnePointZero = function(n) {
|
||||
return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1;
|
||||
};
|
||||
|
||||
const isPercentage = function(n) {
|
||||
return typeof n === 'string' && n.indexOf('%') !== -1;
|
||||
};
|
||||
|
||||
// Take input from [0, n] and return it as [0, 1]
|
||||
const bound01 = function(value, max) {
|
||||
if (isOnePointZero(value)) value = '100%';
|
||||
|
||||
const processPercent = isPercentage(value);
|
||||
value = Math.min(max, Math.max(0, parseFloat(value)));
|
||||
|
||||
// Automatically convert percentage into number
|
||||
if (processPercent) {
|
||||
value = parseInt(value * max, 10) / 100;
|
||||
}
|
||||
|
||||
// Handle floating point rounding errors
|
||||
if ((Math.abs(value - max) < 0.000001)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert into [0, 1] range if it isn't already
|
||||
return (value % max) / parseFloat(max);
|
||||
};
|
||||
|
||||
const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' };
|
||||
|
||||
const toHex = function({ r, g, b }) {
|
||||
const hexOne = function(value) {
|
||||
value = Math.min(Math.round(value), 255);
|
||||
const high = Math.floor(value / 16);
|
||||
const low = value % 16;
|
||||
return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low);
|
||||
};
|
||||
|
||||
if (isNaN(r) || isNaN(g) || isNaN(b)) return '';
|
||||
|
||||
return '#' + hexOne(r) + hexOne(g) + hexOne(b);
|
||||
};
|
||||
|
||||
const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 };
|
||||
|
||||
const parseHexChannel = function(hex) {
|
||||
if (hex.length === 2) {
|
||||
return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]);
|
||||
}
|
||||
|
||||
return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1];
|
||||
};
|
||||
|
||||
const hsl2hsv = function(hue, sat, light) {
|
||||
sat = sat / 100;
|
||||
light = light / 100;
|
||||
let smin = sat;
|
||||
const lmin = Math.max(light, 0.01);
|
||||
let sv;
|
||||
let v;
|
||||
|
||||
light *= 2;
|
||||
sat *= (light <= 1) ? light : 2 - light;
|
||||
smin *= lmin <= 1 ? lmin : 2 - lmin;
|
||||
v = (light + sat) / 2;
|
||||
sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat);
|
||||
|
||||
return {
|
||||
h: hue,
|
||||
s: sv * 100,
|
||||
v: v * 100
|
||||
};
|
||||
};
|
||||
|
||||
// `rgbToHsv`
|
||||
// Converts an RGB color value to HSV
|
||||
// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
|
||||
// *Returns:* { h, s, v } in [0,1]
|
||||
const rgb2hsv = function(r, g, b) {
|
||||
r = bound01(r, 255);
|
||||
g = bound01(g, 255);
|
||||
b = bound01(b, 255);
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h, s;
|
||||
let v = max;
|
||||
|
||||
const d = max - min;
|
||||
s = max === 0 ? 0 : d / max;
|
||||
|
||||
if (max === min) {
|
||||
h = 0; // achromatic
|
||||
} else {
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return { h: h * 360, s: s * 100, v: v * 100 };
|
||||
};
|
||||
|
||||
// `hsvToRgb`
|
||||
// Converts an HSV color value to RGB.
|
||||
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
|
||||
// *Returns:* { r, g, b } in the set [0, 255]
|
||||
const hsv2rgb = function(h, s, v) {
|
||||
h = bound01(h, 360) * 6;
|
||||
s = bound01(s, 100);
|
||||
v = bound01(v, 100);
|
||||
|
||||
const i = Math.floor(h);
|
||||
const f = h - i;
|
||||
const p = v * (1 - s);
|
||||
const q = v * (1 - f * s);
|
||||
const t = v * (1 - (1 - f) * s);
|
||||
const mod = i % 6;
|
||||
const r = [v, q, p, p, t, v][mod];
|
||||
const g = [t, v, v, q, p, p][mod];
|
||||
const b = [p, p, t, v, v, q][mod];
|
||||
|
||||
return {
|
||||
r: Math.round(r * 255),
|
||||
g: Math.round(g * 255),
|
||||
b: Math.round(b * 255)
|
||||
};
|
||||
};
|
||||
|
||||
export default class Color {
|
||||
constructor(options) {
|
||||
this._hue = 0;
|
||||
this._saturation = 100;
|
||||
this._value = 100;
|
||||
this._alpha = 100;
|
||||
|
||||
this.enableAlpha = false;
|
||||
this.format = 'hex';
|
||||
this.value = '';
|
||||
|
||||
options = options || {};
|
||||
|
||||
for (let option in options) {
|
||||
if (options.hasOwnProperty(option)) {
|
||||
this[option] = options[option];
|
||||
}
|
||||
}
|
||||
|
||||
this.doOnChange();
|
||||
}
|
||||
|
||||
set(prop, value) {
|
||||
if (arguments.length === 1 && typeof prop === 'object') {
|
||||
for (let p in prop) {
|
||||
if (prop.hasOwnProperty(p)) {
|
||||
this.set(p, prop[p]);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this['_' + prop] = value;
|
||||
this.doOnChange();
|
||||
}
|
||||
|
||||
get(prop) {
|
||||
return this['_' + prop];
|
||||
}
|
||||
|
||||
toRgb() {
|
||||
return hsv2rgb(this._hue, this._saturation, this._value);
|
||||
}
|
||||
|
||||
fromString(value) {
|
||||
if (!value) {
|
||||
this._hue = 0;
|
||||
this._saturation = 100;
|
||||
this._value = 100;
|
||||
|
||||
this.doOnChange();
|
||||
return;
|
||||
}
|
||||
|
||||
const fromHSV = (h, s, v) => {
|
||||
this._hue = Math.max(0, Math.min(360, h));
|
||||
this._saturation = Math.max(0, Math.min(100, s));
|
||||
this._value = Math.max(0, Math.min(100, v));
|
||||
|
||||
this.doOnChange();
|
||||
};
|
||||
|
||||
if (value.indexOf('hsl') !== -1) {
|
||||
const parts = value.replace(/hsla|hsl|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100);
|
||||
} else if (parts.length === 3) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
const { h, s, v } = hsl2hsv(parts[0], parts[1], parts[2]);
|
||||
fromHSV(h, s, v);
|
||||
}
|
||||
} else if (value.indexOf('hsv') !== -1) {
|
||||
const parts = value.replace(/hsva|hsv|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100);
|
||||
} else if (parts.length === 3) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
fromHSV(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
} else if (value.indexOf('rgb') !== -1) {
|
||||
const parts = value.replace(/rgba|rgb|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100);
|
||||
} else if (parts.length === 3) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
const { h, s, v } = rgb2hsv(parts[0], parts[1], parts[2]);
|
||||
fromHSV(h, s, v);
|
||||
}
|
||||
} else if (value.indexOf('#') !== -1) {
|
||||
const hex = value.replace('#', '').trim();
|
||||
let r, g, b;
|
||||
|
||||
if (hex.length === 3) {
|
||||
r = parseHexChannel(hex[0] + hex[0]);
|
||||
g = parseHexChannel(hex[1] + hex[1]);
|
||||
b = parseHexChannel(hex[2] + hex[2]);
|
||||
} else if (hex.length === 6 || hex.length === 8) {
|
||||
r = parseHexChannel(hex.substring(0, 2));
|
||||
g = parseHexChannel(hex.substring(2, 4));
|
||||
b = parseHexChannel(hex.substring(4, 6));
|
||||
}
|
||||
|
||||
if (hex.length === 8) {
|
||||
this._alpha = Math.floor(parseHexChannel(hex.substring(6)) / 255 * 100);
|
||||
} else if (hex.length === 3 || hex.length === 6) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
|
||||
const { h, s, v } = rgb2hsv(r, g, b);
|
||||
fromHSV(h, s, v);
|
||||
}
|
||||
}
|
||||
|
||||
compare(color) {
|
||||
return Math.abs(color._hue - this._hue) < 2 &&
|
||||
Math.abs(color._saturation - this._saturation) < 1 &&
|
||||
Math.abs(color._value - this._value) < 1 &&
|
||||
Math.abs(color._alpha - this._alpha) < 1;
|
||||
}
|
||||
|
||||
doOnChange() {
|
||||
const { _hue, _saturation, _value, _alpha, format } = this;
|
||||
|
||||
if (this.enableAlpha) {
|
||||
switch (format) {
|
||||
case 'hsl':
|
||||
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
|
||||
this.value = `hsla(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%, ${ _alpha / 100})`;
|
||||
break;
|
||||
case 'hsv':
|
||||
this.value = `hsva(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%, ${ _alpha / 100})`;
|
||||
break;
|
||||
default:
|
||||
const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
|
||||
this.value = `rgba(${r}, ${g}, ${b}, ${ _alpha / 100 })`;
|
||||
}
|
||||
} else {
|
||||
switch (format) {
|
||||
case 'hsl':
|
||||
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
|
||||
this.value = `hsl(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%)`;
|
||||
break;
|
||||
case 'hsv':
|
||||
this.value = `hsv(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%)`;
|
||||
break;
|
||||
case 'rgb':
|
||||
const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
|
||||
this.value = `rgb(${r}, ${g}, ${b})`;
|
||||
break;
|
||||
default:
|
||||
this.value = toHex(hsv2rgb(_hue, _saturation, _value));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="el-color-alpha-slider" :class="{ 'is-vertical': vertical }">
|
||||
<div class="el-color-alpha-slider__bar"
|
||||
@click="handleClick"
|
||||
ref="bar"
|
||||
:style="{
|
||||
background: background
|
||||
}">
|
||||
</div>
|
||||
<div class="el-color-alpha-slider__thumb"
|
||||
ref="thumb"
|
||||
:style="{
|
||||
left: thumbLeft + 'px',
|
||||
top: thumbTop + 'px'
|
||||
}">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from '../draggable';
|
||||
|
||||
export default {
|
||||
name: 'el-color-alpha-slider',
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
vertical: Boolean
|
||||
},
|
||||
|
||||
watch: {
|
||||
'color._alpha'() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
'color.value'() {
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(event) {
|
||||
const thumb = this.$refs.thumb;
|
||||
const target = event.target;
|
||||
|
||||
if (target !== thumb) {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDrag(event) {
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
const { thumb } = this.$refs;
|
||||
|
||||
if (!this.vertical) {
|
||||
let left = event.clientX - rect.left;
|
||||
left = Math.max(thumb.offsetWidth / 2, left);
|
||||
left = Math.min(left, rect.width - thumb.offsetWidth / 2);
|
||||
|
||||
this.color.set('alpha', Math.round((left - thumb.offsetWidth / 2) / (rect.width - thumb.offsetWidth) * 100));
|
||||
} else {
|
||||
let top = event.clientY - rect.top;
|
||||
top = Math.max(thumb.offsetHeight / 2, top);
|
||||
top = Math.min(top, rect.height - thumb.offsetHeight / 2);
|
||||
|
||||
this.color.set('alpha', Math.round((top - thumb.offsetHeight / 2) / (rect.height - thumb.offsetHeight) * 100));
|
||||
}
|
||||
},
|
||||
|
||||
getThumbLeft() {
|
||||
if (this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const alpha = this.color._alpha;
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(alpha * (el.offsetWidth - thumb.offsetWidth / 2) / 100);
|
||||
},
|
||||
|
||||
getThumbTop() {
|
||||
if (!this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const alpha = this.color._alpha;
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(alpha * (el.offsetHeight - thumb.offsetHeight / 2) / 100);
|
||||
},
|
||||
|
||||
getBackground() {
|
||||
if (this.color && this.color.value) {
|
||||
const { r, g, b } = this.color.toRgb();
|
||||
return `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
update() {
|
||||
this.thumbLeft = this.getThumbLeft();
|
||||
this.thumbTop = this.getThumbTop();
|
||||
this.background = this.getBackground();
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
thumbLeft: 0,
|
||||
thumbTop: 0,
|
||||
background: null
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const { bar, thumb } = this.$refs;
|
||||
|
||||
const dragConfig = {
|
||||
drag: (event) => {
|
||||
this.handleDrag(event);
|
||||
},
|
||||
end: (event) => {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
};
|
||||
|
||||
draggable(bar, dragConfig);
|
||||
draggable(thumb, dragConfig);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div class="el-color-predefine color-list-container">
|
||||
<div class="el-color-predefine__colors color-list">
|
||||
<div class="color-list-item"
|
||||
:class="{selected: item.selected, 'is-alpha': item._alpha < 100}"
|
||||
v-for="(item, index) in rgbaColors"
|
||||
:key="colors[index].variable"
|
||||
@click="handleSelect(index)">
|
||||
<span class="color-list-item-ball" :style="{'background-color': item.value}">
|
||||
</span>
|
||||
<div class="color-list-item-label">
|
||||
{{item.info.label}}
|
||||
<div class="color-list-item-value">
|
||||
{{item.info.value}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
.color-list-container {
|
||||
border-top: 1px solid #EBEEF5;
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
.color-list {
|
||||
max-height: 138px;
|
||||
overflow: auto;
|
||||
}
|
||||
.color-list-item {
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
margin: 2px 0;
|
||||
position: relative;
|
||||
}
|
||||
.color-list-item:hover {
|
||||
background: #efefef;
|
||||
}
|
||||
.color-list-item-ball {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-top: 2px;
|
||||
margin-left: 5px;
|
||||
border-radius: 100%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
.color-list-item-label {
|
||||
margin-left: 35px;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
display: inline-block;
|
||||
width: 85%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.color-list-item-value {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Color from '../color';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
colors: { type: Array, required: true },
|
||||
color: { required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rgbaColors: this.parseColors(this.colors, this.color)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSelect(index) {
|
||||
this.color.fromString(this.colors[index].value);
|
||||
this.$emit('select', this.colors[index]);
|
||||
},
|
||||
parseColors(colors, color) {
|
||||
return colors.map(value => {
|
||||
const c = new Color();
|
||||
c.enableAlpha = true;
|
||||
c.format = 'rgba';
|
||||
c.fromString(value.value);
|
||||
c.info = value;
|
||||
c.selected = c.value === color.value;
|
||||
return c;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.currentColor'(val) {
|
||||
const color = new Color();
|
||||
color.fromString(val);
|
||||
|
||||
this.rgbaColors.forEach(item => {
|
||||
item.selected = color.compare(item);
|
||||
});
|
||||
},
|
||||
colors(newVal) {
|
||||
this.rgbaColors = this.parseColors(newVal, this.color);
|
||||
},
|
||||
color(newVal) {
|
||||
this.rgbaColors = this.parseColors(this.colors, newVal);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="el-color-hue-slider" :class="{ 'is-vertical': vertical }">
|
||||
<div class="el-color-hue-slider__bar" @click="handleClick" ref="bar"></div>
|
||||
<div class="el-color-hue-slider__thumb"
|
||||
:style="{
|
||||
left: thumbLeft + 'px',
|
||||
top: thumbTop + 'px'
|
||||
}"
|
||||
ref="thumb">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from '../draggable';
|
||||
|
||||
export default {
|
||||
name: 'el-color-hue-slider',
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
|
||||
vertical: Boolean
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
thumbLeft: 0,
|
||||
thumbTop: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hueValue() {
|
||||
const hue = this.color.get('hue');
|
||||
return hue;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
hueValue() {
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(event) {
|
||||
const thumb = this.$refs.thumb;
|
||||
const target = event.target;
|
||||
|
||||
if (target !== thumb) {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDrag(event) {
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
const { thumb } = this.$refs;
|
||||
let hue;
|
||||
|
||||
if (!this.vertical) {
|
||||
let left = event.clientX - rect.left;
|
||||
left = Math.min(left, rect.width - thumb.offsetWidth / 2);
|
||||
left = Math.max(thumb.offsetWidth / 2, left);
|
||||
|
||||
hue = Math.round((left - thumb.offsetWidth / 2) / (rect.width - thumb.offsetWidth) * 360);
|
||||
} else {
|
||||
let top = event.clientY - rect.top;
|
||||
top = Math.min(top, rect.height - thumb.offsetHeight / 2);
|
||||
top = Math.max(thumb.offsetHeight / 2, top);
|
||||
|
||||
hue = Math.round((top - thumb.offsetHeight / 2) / (rect.height - thumb.offsetHeight) * 360);
|
||||
}
|
||||
|
||||
this.color.set('hue', hue);
|
||||
},
|
||||
|
||||
getThumbLeft() {
|
||||
if (this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const hue = this.color.get('hue');
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(hue * (el.offsetWidth - thumb.offsetWidth / 2) / 360);
|
||||
},
|
||||
|
||||
getThumbTop() {
|
||||
if (!this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const hue = this.color.get('hue');
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(hue * (el.offsetHeight - thumb.offsetHeight / 2) / 360);
|
||||
},
|
||||
|
||||
update() {
|
||||
this.thumbLeft = this.getThumbLeft();
|
||||
this.thumbTop = this.getThumbTop();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const { bar, thumb } = this.$refs;
|
||||
|
||||
const dragConfig = {
|
||||
drag: (event) => {
|
||||
this.handleDrag(event);
|
||||
},
|
||||
end: (event) => {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
};
|
||||
|
||||
draggable(bar, dragConfig);
|
||||
draggable(thumb, dragConfig);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-top" @after-leave="doDestroy">
|
||||
<div
|
||||
class="el-color-dropdown"
|
||||
v-show="showPopper">
|
||||
<div class="el-color-dropdown__main-wrapper">
|
||||
<hue-slider ref="hue" :color="color" vertical style="float: right;"></hue-slider>
|
||||
<sv-panel ref="sl" :color="color"></sv-panel>
|
||||
</div>
|
||||
<alpha-slider v-if="showAlpha" ref="alpha" :color="color"></alpha-slider>
|
||||
<predefine v-if="predefine" :color="color" :colors="predefine"></predefine>
|
||||
<div class="el-color-dropdown__btns">
|
||||
<span class="el-color-dropdown__value">
|
||||
<el-input
|
||||
v-model="customInput"
|
||||
@keyup.native.enter="handleConfirm"
|
||||
@blur="handleConfirm"
|
||||
:validate-event="false"
|
||||
size="mini">
|
||||
</el-input>
|
||||
</span>
|
||||
<!-- <el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
class="el-color-dropdown__link-btn"
|
||||
@click="$emit('clear')">
|
||||
{{ t('el.colorpicker.clear') }}
|
||||
</el-button> -->
|
||||
<el-button
|
||||
plain
|
||||
size="mini"
|
||||
type="primary"
|
||||
class="el-color-dropdown__btn"
|
||||
@click="confirmValue">
|
||||
{{ t('el.colorpicker.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<color-list
|
||||
v-if="colorList && colorList.length > 0"
|
||||
:color="color"
|
||||
:colors="colorList"
|
||||
@select=onColorListSelect
|
||||
></color-list>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvPanel from './sv-panel';
|
||||
import HueSlider from './hue-slider';
|
||||
import AlphaSlider from './alpha-slider';
|
||||
import Predefine from './predefine';
|
||||
import ColorList from './color-list';
|
||||
import Popper from 'element-ui/src/utils/vue-popper';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import ElInput from 'element-ui/packages/input';
|
||||
import ElButton from 'element-ui/packages/button';
|
||||
|
||||
export default {
|
||||
name: 'el-color-picker-dropdown',
|
||||
|
||||
mixins: [Popper, Locale],
|
||||
|
||||
components: {
|
||||
SvPanel,
|
||||
HueSlider,
|
||||
AlphaSlider,
|
||||
ElInput,
|
||||
ElButton,
|
||||
Predefine,
|
||||
ColorList
|
||||
},
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
showAlpha: Boolean,
|
||||
predefine: Array,
|
||||
colorList: Array
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
customInput: ''
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentColor() {
|
||||
const parent = this.$parent;
|
||||
return !parent.value && !parent.showPanelColor ? '' : parent.color.value;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
confirmValue() {
|
||||
this.$emit('pick');
|
||||
},
|
||||
|
||||
onColorListSelect(e) {
|
||||
this.$emit('pick', e);
|
||||
},
|
||||
|
||||
handleConfirm() {
|
||||
this.color.fromString(this.customInput);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$parent.popperElm = this.popperElm = this.$el;
|
||||
this.referenceElm = this.$parent.$el;
|
||||
},
|
||||
|
||||
watch: {
|
||||
showPopper(val) {
|
||||
if (val === true) {
|
||||
this.$nextTick(() => {
|
||||
const { sl, hue, alpha } = this.$refs;
|
||||
sl && sl.update();
|
||||
hue && hue.update();
|
||||
alpha && alpha.update();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
currentColor: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.customInput = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="el-color-predefine">
|
||||
<div class="el-color-predefine__colors">
|
||||
<div class="el-color-predefine__color-selector"
|
||||
:class="{selected: item.selected, 'is-alpha': item._alpha < 100}"
|
||||
v-for="(item, index) in rgbaColors"
|
||||
:key="colors[index]"
|
||||
@click="handleSelect(index)">
|
||||
<div :style="{'background-color': item.value}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Color from '../color';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
colors: { type: Array, required: true },
|
||||
color: { required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rgbaColors: this.parseColors(this.colors, this.color)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSelect(index) {
|
||||
this.color.fromString(this.colors[index]);
|
||||
},
|
||||
parseColors(colors, color) {
|
||||
return colors.map(value => {
|
||||
const c = new Color();
|
||||
c.enableAlpha = true;
|
||||
c.format = 'rgba';
|
||||
c.fromString(value);
|
||||
c.selected = c.value === color.value;
|
||||
return c;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.currentColor'(val) {
|
||||
const color = new Color();
|
||||
color.fromString(val);
|
||||
|
||||
this.rgbaColors.forEach(item => {
|
||||
item.selected = color.compare(item);
|
||||
});
|
||||
},
|
||||
colors(newVal) {
|
||||
this.rgbaColors = this.parseColors(newVal, this.color);
|
||||
},
|
||||
color(newVal) {
|
||||
this.rgbaColors = this.parseColors(this.colors, newVal);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="el-color-svpanel"
|
||||
:style="{
|
||||
backgroundColor: background
|
||||
}">
|
||||
<div class="el-color-svpanel__white"></div>
|
||||
<div class="el-color-svpanel__black"></div>
|
||||
<div class="el-color-svpanel__cursor"
|
||||
:style="{
|
||||
top: cursorTop + 'px',
|
||||
left: cursorLeft + 'px'
|
||||
}">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from '../draggable';
|
||||
|
||||
export default {
|
||||
name: 'el-sl-panel',
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
colorValue() {
|
||||
const hue = this.color.get('hue');
|
||||
const value = this.color.get('value');
|
||||
return { hue, value };
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
colorValue() {
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const saturation = this.color.get('saturation');
|
||||
const value = this.color.get('value');
|
||||
|
||||
const el = this.$el;
|
||||
let { clientWidth: width, clientHeight: height } = el;
|
||||
|
||||
this.cursorLeft = saturation * width / 100;
|
||||
this.cursorTop = (100 - value) * height / 100;
|
||||
|
||||
this.background = 'hsl(' + this.color.get('hue') + ', 100%, 50%)';
|
||||
},
|
||||
|
||||
handleDrag(event) {
|
||||
const el = this.$el;
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
let left = event.clientX - rect.left;
|
||||
let top = event.clientY - rect.top;
|
||||
left = Math.max(0, left);
|
||||
left = Math.min(left, rect.width);
|
||||
|
||||
top = Math.max(0, top);
|
||||
top = Math.min(top, rect.height);
|
||||
|
||||
this.cursorLeft = left;
|
||||
this.cursorTop = top;
|
||||
this.color.set({
|
||||
saturation: left / rect.width * 100,
|
||||
value: 100 - top / rect.height * 100
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
draggable(this.$el, {
|
||||
drag: (event) => {
|
||||
this.handleDrag(event);
|
||||
},
|
||||
end: (event) => {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
});
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cursorTop: 0,
|
||||
cursorLeft: 0,
|
||||
background: 'hsl(0, 100%, 50%)'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,36 @@
|
||||
import Vue from 'vue';
|
||||
let isDragging = false;
|
||||
|
||||
export default function(element, options) {
|
||||
if (Vue.prototype.$isServer) return;
|
||||
const moveFn = function(event) {
|
||||
if (options.drag) {
|
||||
options.drag(event);
|
||||
}
|
||||
};
|
||||
const upFn = function(event) {
|
||||
document.removeEventListener('mousemove', moveFn);
|
||||
document.removeEventListener('mouseup', upFn);
|
||||
document.onselectstart = null;
|
||||
document.ondragstart = null;
|
||||
|
||||
isDragging = false;
|
||||
|
||||
if (options.end) {
|
||||
options.end(event);
|
||||
}
|
||||
};
|
||||
element.addEventListener('mousedown', function(event) {
|
||||
if (isDragging) return;
|
||||
document.onselectstart = function() { return false; };
|
||||
document.ondragstart = function() { return false; };
|
||||
|
||||
document.addEventListener('mousemove', moveFn);
|
||||
document.addEventListener('mouseup', upFn);
|
||||
isDragging = true;
|
||||
|
||||
if (options.start) {
|
||||
options.start(event);
|
||||
}
|
||||
});
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'el-color-picker',
|
||||
colorDisabled ? 'is-disabled' : '',
|
||||
colorSize ? `el-color-picker--${ colorSize }` : ''
|
||||
]"
|
||||
v-clickoutside="hide">
|
||||
<div class="el-color-picker__mask" v-if="colorDisabled"></div>
|
||||
<div class="el-color-picker__trigger" @click="handleTrigger">
|
||||
<span class="el-color-picker__color" :class="{ 'is-alpha': showAlpha }">
|
||||
<span class="el-color-picker__color-inner"
|
||||
:style="{
|
||||
backgroundColor: displayedColor
|
||||
}"></span>
|
||||
<span class="el-color-picker__empty el-icon-close" v-if="!value && !showPanelColor"></span>
|
||||
</span>
|
||||
<span class="el-color-picker__icon el-icon-arrow-down" v-show="value || showPanelColor"></span>
|
||||
</div>
|
||||
<picker-dropdown
|
||||
ref="dropdown"
|
||||
:class="['el-color-picker__panel', popperClass || '']"
|
||||
v-model="showPicker"
|
||||
@pick="confirmValue"
|
||||
@clear="clearValue"
|
||||
:color="color"
|
||||
:show-alpha="showAlpha"
|
||||
:predefine="predefine"
|
||||
:colorList="colorList">
|
||||
</picker-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Color from './color';
|
||||
import PickerDropdown from './components/picker-dropdown.vue';
|
||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||
import Emitter from 'element-ui/src/mixins/emitter';
|
||||
|
||||
export default {
|
||||
name: 'ElColorPicker',
|
||||
|
||||
mixins: [Emitter],
|
||||
|
||||
props: {
|
||||
value: String,
|
||||
showAlpha: Boolean,
|
||||
colorFormat: String,
|
||||
disabled: Boolean,
|
||||
size: String,
|
||||
popperClass: String,
|
||||
predefine: Array,
|
||||
colorList: Array
|
||||
},
|
||||
|
||||
inject: {
|
||||
elForm: {
|
||||
default: ''
|
||||
},
|
||||
elFormItem: {
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
directives: { Clickoutside },
|
||||
|
||||
computed: {
|
||||
displayedColor() {
|
||||
if (!this.value && !this.showPanelColor) {
|
||||
return 'transparent';
|
||||
}
|
||||
|
||||
return this.displayedRgb(this.color, this.showAlpha);
|
||||
},
|
||||
|
||||
_elFormItemSize() {
|
||||
return (this.elFormItem || {}).elFormItemSize;
|
||||
},
|
||||
|
||||
colorSize() {
|
||||
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
|
||||
},
|
||||
|
||||
colorDisabled() {
|
||||
return this.disabled || (this.elForm || {}).disabled;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!val) {
|
||||
this.showPanelColor = false;
|
||||
} else if (val && val !== this.color.value) {
|
||||
this.color.fromString(val);
|
||||
}
|
||||
},
|
||||
color: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.showPanelColor = true;
|
||||
}
|
||||
},
|
||||
displayedColor(val) {
|
||||
if (!this.showPicker) return;
|
||||
const currentValueColor = new Color({
|
||||
enableAlpha: this.showAlpha,
|
||||
format: this.colorFormat
|
||||
});
|
||||
currentValueColor.fromString(this.value);
|
||||
|
||||
const currentValueColorRgb = this.displayedRgb(currentValueColor, this.showAlpha);
|
||||
if (val !== currentValueColorRgb) {
|
||||
this.$emit('active-change', val);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleTrigger() {
|
||||
if (this.colorDisabled) return;
|
||||
this.showPicker = !this.showPicker;
|
||||
},
|
||||
confirmValue(selection) {
|
||||
const value = selection || this.color.value;
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value);
|
||||
this.dispatch('ElFormItem', 'el.form.change', value);
|
||||
this.showPicker = false;
|
||||
},
|
||||
clearValue() {
|
||||
this.$emit('input', null);
|
||||
this.$emit('change', null);
|
||||
if (this.value !== null) {
|
||||
this.dispatch('ElFormItem', 'el.form.change', null);
|
||||
}
|
||||
this.showPanelColor = false;
|
||||
this.showPicker = false;
|
||||
this.resetColor();
|
||||
},
|
||||
hide() {
|
||||
this.showPicker = false;
|
||||
this.resetColor();
|
||||
},
|
||||
resetColor() {
|
||||
this.$nextTick(_ => {
|
||||
if (this.value) {
|
||||
this.color.fromString(this.value);
|
||||
} else {
|
||||
this.showPanelColor = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
displayedRgb(color, showAlpha) {
|
||||
if (!(color instanceof Color)) {
|
||||
throw Error('color should be instance of Color Class');
|
||||
}
|
||||
|
||||
const { r, g, b } = color.toRgb();
|
||||
return showAlpha
|
||||
? `rgba(${ r }, ${ g }, ${ b }, ${ color.get('alpha') / 100 })`
|
||||
: `rgb(${ r }, ${ g }, ${ b })`;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const value = this.value;
|
||||
if (value) {
|
||||
this.color.fromString(value);
|
||||
}
|
||||
this.popperElm = this.$refs.dropdown.$el;
|
||||
},
|
||||
|
||||
data() {
|
||||
const color = new Color({
|
||||
enableAlpha: this.showAlpha,
|
||||
format: this.colorFormat
|
||||
});
|
||||
|
||||
return {
|
||||
color,
|
||||
showPicker: false,
|
||||
showPanelColor: false
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
PickerDropdown
|
||||
}
|
||||
};
|
||||
</script>
|
93
examples/components/theme-configurator/editor/color.vue
Normal file
93
examples/components/theme-configurator/editor/color.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<div class="content-80">
|
||||
<el-input
|
||||
size="medium"
|
||||
:value=displayValue
|
||||
readonly
|
||||
slot="reference"
|
||||
@click.native="onInputClick"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<color-picker
|
||||
size="medium"
|
||||
ref="colorPicker"
|
||||
class="colorPicker"
|
||||
v-model="pickerColor"
|
||||
@change=onPickerChange
|
||||
:colorList="golbalColorList"
|
||||
></color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.colorPicker {
|
||||
margin-left: 10px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayValue, getStyleDisplayName } from '../utils/utils.js';
|
||||
import ColorPicker from './color-picker';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColorPicker
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pickerColor: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
watch: {
|
||||
displayValue: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (value.startsWith('#')) {
|
||||
this.pickerColor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
golbalColor() {
|
||||
return this.golbalValue.color;
|
||||
},
|
||||
displayValue() {
|
||||
return getStyleDisplayValue(this.mergedValue, this.golbalColor);
|
||||
},
|
||||
golbalColorList() {
|
||||
return this.isGlobal ? [] : Object.keys(this.golbalColor).map((c) => (
|
||||
{
|
||||
label: getStyleDisplayName(this.golbalColor[c]),
|
||||
value: this.golbalColor[c].value,
|
||||
variable: c
|
||||
}
|
||||
));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInputClick() {
|
||||
this.$refs.colorPicker && this.$refs.colorPicker.handleTrigger();
|
||||
},
|
||||
onPickerChange(e) {
|
||||
this.onChange(e.variable || e);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
104
examples/components/theme-configurator/editor/fontLineHeight.vue
Normal file
104
examples/components/theme-configurator/editor/fontLineHeight.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<el-select
|
||||
v-model="value"
|
||||
class="select"
|
||||
size="medium"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const defaultFontLineHeight = [
|
||||
'1',
|
||||
'1.3',
|
||||
'1.5',
|
||||
'1.7',
|
||||
'12px',
|
||||
'16px',
|
||||
'20px',
|
||||
'24px',
|
||||
'28px'
|
||||
];
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
componentName: {
|
||||
type: String
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
defaultFontLineHeight.forEach((size) => {
|
||||
this.options.push({
|
||||
value: size,
|
||||
label: size
|
||||
});
|
||||
});
|
||||
const golbalTypography = this.golbalValue.typography;
|
||||
if (this.isGlobalInputValue && golbalTypography) {
|
||||
Object.keys(golbalTypography).forEach((font) => {
|
||||
if (font.includes('font-line-height')) {
|
||||
const size = golbalTypography[font];
|
||||
this.options.push({
|
||||
value: size.key,
|
||||
label: getStyleDisplayName(size)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
105
examples/components/theme-configurator/editor/fontSize.vue
Normal file
105
examples/components/theme-configurator/editor/fontSize.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<el-select
|
||||
v-model="value"
|
||||
class="select"
|
||||
size="medium"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const defaultFontSize = [
|
||||
'12px',
|
||||
'13px',
|
||||
'14px',
|
||||
'16px',
|
||||
'18px',
|
||||
'20px',
|
||||
'22px',
|
||||
'28px',
|
||||
'36px',
|
||||
'48px'
|
||||
];
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
componentName: {
|
||||
type: String
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
defaultFontSize.forEach((size) => {
|
||||
this.options.push({
|
||||
value: size,
|
||||
label: size
|
||||
});
|
||||
});
|
||||
const golbalTypography = this.golbalValue.typography;
|
||||
if (this.isGlobalInputValue && golbalTypography) {
|
||||
Object.keys(golbalTypography).forEach((font) => {
|
||||
if (font.includes('font-size')) {
|
||||
const size = golbalTypography[font];
|
||||
this.options.push({
|
||||
value: size.key,
|
||||
label: getStyleDisplayName(size)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
109
examples/components/theme-configurator/editor/fontWeight.vue
Normal file
109
examples/components/theme-configurator/editor/fontWeight.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<el-select
|
||||
v-model="value"
|
||||
class="select"
|
||||
size="medium"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const defaultFontWeight = [
|
||||
'normal',
|
||||
'bold',
|
||||
'bolder',
|
||||
'lighter',
|
||||
'100',
|
||||
'200',
|
||||
'300',
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
'800',
|
||||
'900',
|
||||
'inherit'
|
||||
];
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
componentName: {
|
||||
type: String
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
defaultFontWeight.forEach((weight) => {
|
||||
this.options.push({
|
||||
value: weight,
|
||||
label: weight
|
||||
});
|
||||
});
|
||||
const golbalTypography = this.golbalValue.typography;
|
||||
if (this.isGlobalInputValue && golbalTypography) {
|
||||
Object.keys(golbalTypography).forEach((font) => {
|
||||
if (font.includes('font-weight')) {
|
||||
const weight = golbalTypography[font];
|
||||
this.options.push({
|
||||
value: weight.key,
|
||||
label: getStyleDisplayName(weight)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
45
examples/components/theme-configurator/editor/input.vue
Normal file
45
examples/components/theme-configurator/editor/input.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<el-input
|
||||
@keyup.enter.native="onUpdate"
|
||||
v-model="value"
|
||||
@blur="onUpdate"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template slot="suffix">
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['val', 'onChange'],
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
oldValue: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onUpdate(e) {
|
||||
const { value } = e.target;
|
||||
if (value !== this.oldValue) {
|
||||
this.oldValue = value;
|
||||
this.$emit('change', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
val: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.value = value;
|
||||
if (!this.oldValue) {
|
||||
this.oldValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
81
examples/components/theme-configurator/editor/mixin.vue
Normal file
81
examples/components/theme-configurator/editor/mixin.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<style>
|
||||
.config {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.config-label {
|
||||
color: #606266;;
|
||||
font-size: 14px;
|
||||
padding-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
.config-content {
|
||||
|
||||
}
|
||||
.content-80 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
}
|
||||
.content-20 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.content-10 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 10%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.content-15 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 15%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
export default {
|
||||
props: {
|
||||
config: {
|
||||
type: Object
|
||||
},
|
||||
userConfig: {
|
||||
type: Object
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
},
|
||||
componentName: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mergedValue() {
|
||||
return this.userConfig[this.config.key] || this.config.value;
|
||||
},
|
||||
displayName() {
|
||||
return getStyleDisplayName(this.config, this.componentName);
|
||||
},
|
||||
displayKeyName() {
|
||||
if (this.config.name) {
|
||||
return this.config.key.replace('$--', '');
|
||||
}
|
||||
return this.config.key.replace(`$--${this.componentName}-`, '');
|
||||
},
|
||||
isGlobal() {
|
||||
return !this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(value) {
|
||||
this.$emit('onChange', {
|
||||
key: this.config.key,
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
41
examples/components/theme-configurator/editor/simpleText.vue
Normal file
41
examples/components/theme-configurator/editor/simpleText.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<theme-input
|
||||
:val="value"
|
||||
size="medium"
|
||||
@change="onChange"
|
||||
></theme-input>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Input from './input';
|
||||
import Mixin from './mixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
themeInput: Input
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user