push
This commit is contained in:
138
lib/component/color/common/Alpha.vue
Normal file
138
lib/component/color/common/Alpha.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="vc-alpha">
|
||||
<div class="vc-alpha-checkboard-wrap">
|
||||
<checkboard></checkboard>
|
||||
</div>
|
||||
<div class="vc-alpha-gradient" :style="{ background: gradientColor }"></div>
|
||||
<div
|
||||
class="vc-alpha-container"
|
||||
ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange"
|
||||
>
|
||||
<div class="vc-alpha-pointer" :style="{ left: colors.a * 100 + '%' }">
|
||||
<div class="vc-alpha-picker"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import checkboard from './Checkboard.vue';
|
||||
|
||||
export default {
|
||||
name: 'Alpha',
|
||||
props: {
|
||||
value: Object,
|
||||
onChange: Function,
|
||||
},
|
||||
components: {
|
||||
checkboard,
|
||||
},
|
||||
computed: {
|
||||
colors() {
|
||||
return this.value;
|
||||
},
|
||||
gradientColor() {
|
||||
var rgba = this.colors.rgba;
|
||||
var rgbStr = [rgba.r, rgba.g, rgba.b].join(',');
|
||||
return (
|
||||
'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)'
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange(e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
var container = this.$refs.container;
|
||||
if (!container) {
|
||||
// for some edge cases, container may not exist. see #220
|
||||
return;
|
||||
}
|
||||
var containerWidth = container.clientWidth;
|
||||
|
||||
var xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
var pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
var left = pageX - xOffset;
|
||||
|
||||
var a;
|
||||
if (left < 0) {
|
||||
a = 0;
|
||||
} else if (left > containerWidth) {
|
||||
a = 1;
|
||||
} else {
|
||||
a = Math.round((left * 100) / containerWidth) / 100;
|
||||
}
|
||||
|
||||
if (this.colors.a !== a) {
|
||||
this.$emit('change', {
|
||||
h: this.colors.hsl.h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: a,
|
||||
source: 'rgba',
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
this.handleChange(e, true);
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp() {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners() {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-alpha {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.vc-alpha-checkboard-wrap {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.vc-alpha-gradient {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.vc-alpha-container {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 100%;
|
||||
margin: 0 3px;
|
||||
}
|
||||
.vc-alpha-pointer {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
}
|
||||
.vc-alpha-picker {
|
||||
cursor: pointer;
|
||||
width: 4px;
|
||||
border-radius: 1px;
|
||||
height: 8px;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
|
||||
background: #fff;
|
||||
margin-top: 1px;
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
</style>
|
||||
92
lib/component/color/common/Checkboard.vue
Normal file
92
lib/component/color/common/Checkboard.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="vc-checkerboard" :style="bgStyle"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let _checkboardCache = {};
|
||||
|
||||
export default {
|
||||
name: 'Checkboard',
|
||||
props: {
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 8,
|
||||
},
|
||||
white: {
|
||||
type: String,
|
||||
default: '#fff',
|
||||
},
|
||||
grey: {
|
||||
type: String,
|
||||
default: '#e6e6e6',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
bgStyle() {
|
||||
return {
|
||||
'background-image': 'url(' + getCheckboard(this.white, this.grey, this.size) + ')',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* get base 64 data by canvas
|
||||
*
|
||||
* @param {String} c1 hex color
|
||||
* @param {String} c2 hex color
|
||||
* @param {Number} size
|
||||
*/
|
||||
|
||||
function renderCheckboard(c1, c2, size) {
|
||||
// Dont Render On Server
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = canvas.height = size * 2;
|
||||
var ctx = canvas.getContext('2d');
|
||||
// If no context can be found, return early.
|
||||
if (!ctx) {
|
||||
return null;
|
||||
}
|
||||
ctx.fillStyle = c1;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = c2;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
ctx.translate(size, size);
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* get checkboard base data and cache
|
||||
*
|
||||
* @param {String} c1 hex color
|
||||
* @param {String} c2 hex color
|
||||
* @param {Number} size
|
||||
*/
|
||||
|
||||
function getCheckboard(c1, c2, size) {
|
||||
var key = c1 + ',' + c2 + ',' + size;
|
||||
|
||||
if (_checkboardCache[key]) {
|
||||
return _checkboardCache[key];
|
||||
} else {
|
||||
var checkboard = renderCheckboard(c1, c2, size);
|
||||
_checkboardCache[key] = checkboard;
|
||||
return checkboard;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-checkerboard {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
background-size: contain;
|
||||
}
|
||||
</style>
|
||||
114
lib/component/color/common/EditableInput.vue
Normal file
114
lib/component/color/common/EditableInput.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="vc-editable-input">
|
||||
<input
|
||||
:aria-labelledby="labelId"
|
||||
class="vc-input__input"
|
||||
v-model="val"
|
||||
@keydown="handleKeyDown"
|
||||
@input="update"
|
||||
ref="input"
|
||||
/>
|
||||
<span :for="label" class="vc-input__label" :id="labelId">{{ labelSpanText }}</span>
|
||||
<span class="vc-input__desc">{{ desc }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'editableInput',
|
||||
props: {
|
||||
label: String,
|
||||
labelText: String,
|
||||
desc: String,
|
||||
value: [String, Number],
|
||||
max: Number,
|
||||
min: Number,
|
||||
arrowOffset: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
val: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(v) {
|
||||
// TODO: min
|
||||
if (!(this.max === undefined) && +v > this.max) {
|
||||
this.$refs.input.value = this.max;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
},
|
||||
},
|
||||
labelId() {
|
||||
return `input__label__${this.label}__${Math.random().toString().slice(2, 5)}`;
|
||||
},
|
||||
labelSpanText() {
|
||||
return this.labelText || this.label;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
update(e) {
|
||||
this.handleChange(e.target.value);
|
||||
},
|
||||
handleChange(newVal) {
|
||||
let data = {};
|
||||
data[this.label] = newVal;
|
||||
if (data.hex === undefined && data['#'] === undefined) {
|
||||
this.$emit('change', data);
|
||||
} else if (newVal.length > 5) {
|
||||
this.$emit('change', data);
|
||||
}
|
||||
},
|
||||
// **** unused
|
||||
// handleBlur (e) {
|
||||
// console.log(e)
|
||||
// },
|
||||
handleKeyDown(e) {
|
||||
let val = this.val;
|
||||
let number = Number(val);
|
||||
|
||||
if (number) {
|
||||
let amount = this.arrowOffset || 1;
|
||||
|
||||
// Up
|
||||
if (e.keyCode === 38) {
|
||||
val = number + amount;
|
||||
this.handleChange(val);
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Down
|
||||
if (e.keyCode === 40) {
|
||||
val = number - amount;
|
||||
this.handleChange(val);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
// **** unused
|
||||
// handleDrag (e) {
|
||||
// console.log(e)
|
||||
// },
|
||||
// handleMouseDown (e) {
|
||||
// console.log(e)
|
||||
// }
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-editable-input {
|
||||
position: relative;
|
||||
}
|
||||
.vc-input__input {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
.vc-input__label {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
||||
205
lib/component/color/common/Hue.vue
Normal file
205
lib/component/color/common/Hue.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<div :class="['vc-hue', directionClass]">
|
||||
<div
|
||||
class="vc-hue-container"
|
||||
role="slider"
|
||||
:aria-valuenow="colors.hsl.h"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="360"
|
||||
ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange"
|
||||
>
|
||||
<div
|
||||
class="vc-hue-pointer"
|
||||
:style="{ top: pointerTop, left: pointerLeft }"
|
||||
role="presentation"
|
||||
>
|
||||
<div class="vc-hue-picker"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hue',
|
||||
props: {
|
||||
value: Object,
|
||||
direction: {
|
||||
type: String,
|
||||
// [horizontal | vertical]
|
||||
default: 'horizontal',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
oldHue: 0,
|
||||
pullDirection: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
colors() {
|
||||
const h = this.value.hsl.h;
|
||||
if (h !== 0 && h - this.oldHue > 0) this.pullDirection = 'right';
|
||||
if (h !== 0 && h - this.oldHue < 0) this.pullDirection = 'left';
|
||||
this.oldHue = h;
|
||||
|
||||
return this.value;
|
||||
},
|
||||
directionClass() {
|
||||
return {
|
||||
'vc-hue--horizontal': this.direction === 'horizontal',
|
||||
'vc-hue--vertical': this.direction === 'vertical',
|
||||
};
|
||||
},
|
||||
pointerTop() {
|
||||
if (this.direction === 'vertical') {
|
||||
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return 0;
|
||||
return -((this.colors.hsl.h * 100) / 360) + 100 + '%';
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
pointerLeft() {
|
||||
if (this.direction === 'vertical') {
|
||||
return 0;
|
||||
} else {
|
||||
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return '100%';
|
||||
return (this.colors.hsl.h * 100) / 360 + '%';
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange(e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
|
||||
var container = this.$refs.container;
|
||||
if (!container) {
|
||||
// for some edge cases, container may not exist. see #220
|
||||
return;
|
||||
}
|
||||
var containerWidth = container.clientWidth;
|
||||
var containerHeight = container.clientHeight;
|
||||
|
||||
var xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
var yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||
var pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
var pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
|
||||
var left = pageX - xOffset;
|
||||
var top = pageY - yOffset;
|
||||
|
||||
var h;
|
||||
var percent;
|
||||
|
||||
if (this.direction === 'vertical') {
|
||||
if (top < 0) {
|
||||
h = 360;
|
||||
} else if (top > containerHeight) {
|
||||
h = 0;
|
||||
} else {
|
||||
percent = -((top * 100) / containerHeight) + 100;
|
||||
h = (360 * percent) / 100;
|
||||
}
|
||||
|
||||
if (this.colors.hsl.h !== h) {
|
||||
this.$emit('change', {
|
||||
h: h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: this.colors.hsl.a,
|
||||
source: 'hsl',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (left < 0) {
|
||||
h = 0;
|
||||
} else if (left > containerWidth) {
|
||||
h = 360;
|
||||
} else {
|
||||
percent = (left * 100) / containerWidth;
|
||||
h = (360 * percent) / 100;
|
||||
}
|
||||
|
||||
if (this.colors.hsl.h !== h) {
|
||||
this.$emit('change', {
|
||||
h: h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: this.colors.hsl.a,
|
||||
source: 'hsl',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
this.handleChange(e, true);
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp(e) {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners() {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-hue {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.vc-hue--horizontal {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#f00 0%,
|
||||
#ff0 17%,
|
||||
#0f0 33%,
|
||||
#0ff 50%,
|
||||
#00f 67%,
|
||||
#f0f 83%,
|
||||
#f00 100%
|
||||
);
|
||||
}
|
||||
.vc-hue--vertical {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
#f00 0%,
|
||||
#ff0 17%,
|
||||
#0f0 33%,
|
||||
#0ff 50%,
|
||||
#00f 67%,
|
||||
#f0f 83%,
|
||||
#f00 100%
|
||||
);
|
||||
}
|
||||
.vc-hue-container {
|
||||
cursor: pointer;
|
||||
margin: 0 2px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
.vc-hue-pointer {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
}
|
||||
.vc-hue-picker {
|
||||
cursor: pointer;
|
||||
margin-top: 1px;
|
||||
width: 4px;
|
||||
border-radius: 1px;
|
||||
height: 20px;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
|
||||
background: #fff;
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
</style>
|
||||
132
lib/component/color/common/Saturation.vue
Normal file
132
lib/component/color/common/Saturation.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div
|
||||
class="vc-saturation"
|
||||
:style="{ background: bgColor }"
|
||||
ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange"
|
||||
>
|
||||
<div class="vc-saturation--white"></div>
|
||||
<div class="vc-saturation--black"></div>
|
||||
<div class="vc-saturation-pointer" :style="{ top: pointerTop, left: pointerLeft }">
|
||||
<div class="vc-saturation-circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import clamp from 'clamp';
|
||||
import { throttle } from 'lodash-es';
|
||||
|
||||
export default {
|
||||
name: 'Saturation',
|
||||
props: {
|
||||
value: Object,
|
||||
},
|
||||
computed: {
|
||||
colors() {
|
||||
return this.value;
|
||||
},
|
||||
bgColor() {
|
||||
return `hsl(${this.colors.hsv.h}, 100%, 50%)`;
|
||||
},
|
||||
pointerTop() {
|
||||
return -(this.colors.hsv.v * 100) + 1 + 100 + '%';
|
||||
},
|
||||
pointerLeft() {
|
||||
return this.colors.hsv.s * 100 + '%';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
throttle: throttle(
|
||||
(fn, data) => {
|
||||
fn(data);
|
||||
},
|
||||
20,
|
||||
{
|
||||
leading: true,
|
||||
trailing: false,
|
||||
}
|
||||
),
|
||||
handleChange(e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
var container = this.$refs.container;
|
||||
if (!container) {
|
||||
// for some edge cases, container may not exist. see #220
|
||||
return;
|
||||
}
|
||||
var containerWidth = container.clientWidth;
|
||||
var containerHeight = container.clientHeight;
|
||||
|
||||
var xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
var yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||
var pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
var pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
|
||||
var left = clamp(pageX - xOffset, 0, containerWidth);
|
||||
var top = clamp(pageY - yOffset, 0, containerHeight);
|
||||
var saturation = left / containerWidth;
|
||||
var bright = clamp(-(top / containerHeight) + 1, 0, 1);
|
||||
|
||||
this.throttle(this.onChange, {
|
||||
h: this.colors.hsv.h,
|
||||
s: saturation,
|
||||
v: bright,
|
||||
a: this.colors.hsv.a,
|
||||
source: 'hsva',
|
||||
});
|
||||
},
|
||||
onChange(param) {
|
||||
this.$emit('change', param);
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
// this.handleChange(e, true)
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp(e) {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners() {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
},
|
||||
created() {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-saturation,
|
||||
.vc-saturation--white,
|
||||
.vc-saturation--black {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.vc-saturation--white {
|
||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
||||
}
|
||||
.vc-saturation--black {
|
||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
||||
}
|
||||
.vc-saturation-pointer {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
}
|
||||
.vc-saturation-circle {
|
||||
cursor: head;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0, 0, 0, 0.3),
|
||||
0 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
transform: translate(-2px, -2px);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user