push
This commit is contained in:
288
lib/component/color/color-picker.vue
Normal file
288
lib/component/color/color-picker.vue
Normal file
@@ -0,0 +1,288 @@
|
||||
<template>
|
||||
<div :class="['vc-sketch', disableAlpha ? 'vc-sketch__disable-alpha' : '']">
|
||||
<div class="vc-sketch-saturation-wrap">
|
||||
<saturation v-model:value="colors" @change="childChange" />
|
||||
</div>
|
||||
<div class="vc-sketch-controls">
|
||||
<div class="vc-sketch-sliders">
|
||||
<div class="vc-sketch-hue-wrap">
|
||||
<hue v-model:value="colors" @change="childChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="vc-sketch-color-wrap">
|
||||
<div
|
||||
:aria-label="`Current color is ${activeColor}`"
|
||||
class="vc-sketch-active-color"
|
||||
:style="{ background: activeColor }"
|
||||
></div>
|
||||
<check-board />
|
||||
</div>
|
||||
</div>
|
||||
<div class="vc-sketch-field" v-if="!disableFields">
|
||||
<!-- rgba -->
|
||||
<div class="vc-sketch-field--double">
|
||||
<edit-input label="hex" :value="hex" @change="inputChange" />
|
||||
</div>
|
||||
<div class="vc-sketch-field--single">
|
||||
<edit-input label="r" :value="colors.rgba.r" @change="inputChange" />
|
||||
</div>
|
||||
<div class="vc-sketch-field--single">
|
||||
<edit-input label="g" :value="colors.rgba.g" @change="inputChange" />
|
||||
</div>
|
||||
<div class="vc-sketch-field--single">
|
||||
<edit-input label="b" :value="colors.rgba.b" @change="inputChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="vc-sketch-presets"
|
||||
role="group"
|
||||
aria-label="A color preset, pick one to set as current color"
|
||||
>
|
||||
<template v-for="c in presetColors" :key="c">
|
||||
<div
|
||||
v-if="!isTransparent(c)"
|
||||
class="vc-sketch-presets-color"
|
||||
:aria-label="'Color:' + c"
|
||||
:style="{ background: c }"
|
||||
@click="handlePreset(c)"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:aria-label="'Color:' + c"
|
||||
class="vc-sketch-presets-color"
|
||||
@click="handlePreset(c)"
|
||||
>
|
||||
<check-board />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import colorMixin from './mixin/color';
|
||||
import EditInput from './common/EditableInput.vue';
|
||||
import saturation from './common/Saturation.vue';
|
||||
import hue from './common/Hue.vue';
|
||||
import CheckBoard from './common/Checkboard.vue';
|
||||
|
||||
const presetColors = [
|
||||
'#D0021B',
|
||||
'#F5A623',
|
||||
'#F8E71C',
|
||||
'#8B572A',
|
||||
'#7ED321',
|
||||
'#417505',
|
||||
'#BD10E0',
|
||||
'#4A90E2',
|
||||
'#50E3C2',
|
||||
'#B8E986',
|
||||
'#000000',
|
||||
'#4A4A4A',
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'NsColorPicker',
|
||||
components: {
|
||||
saturation,
|
||||
hue,
|
||||
EditInput,
|
||||
CheckBoard,
|
||||
},
|
||||
mixins: [colorMixin],
|
||||
props: {
|
||||
presetColors: {
|
||||
type: Array,
|
||||
default() {
|
||||
return presetColors;
|
||||
},
|
||||
},
|
||||
disableAlpha: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disableFields: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hex() {
|
||||
let hex;
|
||||
if (this.colors.a < 1) {
|
||||
hex = this.colors.hex8;
|
||||
} else {
|
||||
hex = this.colors.hex;
|
||||
}
|
||||
return hex.replace('#', '');
|
||||
},
|
||||
activeColor() {
|
||||
var rgba = this.colors.rgba;
|
||||
return 'rgba(' + [rgba.r, rgba.g, rgba.b, rgba.a].join(',') + ')';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handlePreset(c) {
|
||||
this.colorChange({
|
||||
hex: c,
|
||||
source: 'hex',
|
||||
});
|
||||
},
|
||||
childChange(data) {
|
||||
this.colorChange(data);
|
||||
},
|
||||
inputChange(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (data.hex) {
|
||||
this.isValidHex(data.hex) &&
|
||||
this.colorChange({
|
||||
hex: data.hex,
|
||||
source: 'hex',
|
||||
});
|
||||
} else if (data.r || data.g || data.b || data.a) {
|
||||
this.colorChange({
|
||||
r: data.r || this.colors.rgba.r,
|
||||
g: data.g || this.colors.rgba.g,
|
||||
b: data.b || this.colors.rgba.b,
|
||||
a: data.a || this.colors.rgba.a,
|
||||
source: 'rgba',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-sketch {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
padding: 10px 10px 0;
|
||||
box-sizing: initial;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.vc-sketch-saturation-wrap {
|
||||
width: 100%;
|
||||
padding-bottom: 75%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vc-sketch-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vc-sketch-sliders {
|
||||
padding: 4px 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vc-sketch-sliders .vc-hue,
|
||||
.vc-sketch-sliders .vc-alpha-gradient {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.vc-sketch-hue-wrap {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.vc-sketch-alpha-wrap {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vc-sketch-color-wrap {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
margin-top: 4px;
|
||||
margin-left: 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vc-sketch-active-color {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 2px;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15), inset 0 0 4px rgba(0, 0, 0, 0.25);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.vc-sketch-color-wrap .vc-checkerboard {
|
||||
background-size: auto;
|
||||
}
|
||||
|
||||
.vc-sketch-field {
|
||||
display: flex;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.vc-sketch-field .vc-input__input {
|
||||
width: 90%;
|
||||
padding: 2px 0;
|
||||
text-align: center;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 1px #ccc;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.vc-sketch-field .vc-input__label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: #222;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 4px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.vc-sketch-field--single {
|
||||
flex: 1;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.vc-sketch-field--double {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.vc-sketch-presets {
|
||||
margin-right: -10px;
|
||||
margin-left: -10px;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.vc-sketch-presets-color {
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0 7px 10px 0;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.vc-sketch-presets-color .vc-checkerboard {
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vc-sketch__disable-alpha .vc-sketch-color-wrap {
|
||||
height: 10px;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
4
lib/component/color/index.ts
Normal file
4
lib/component/color/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import ColorPicker from './color-picker.vue';
|
||||
import { withInstall } from '/nerv-lib/util';
|
||||
|
||||
export const NsColorPicker = withInstall(ColorPicker);
|
||||
117
lib/component/color/mixin/color.ts
Normal file
117
lib/component/color/mixin/color.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
function _colorChange(data, oldHue) {
|
||||
const alpha = data && data.a;
|
||||
let color;
|
||||
|
||||
// hsl is better than hex between conversions
|
||||
if (data && data.hsl) {
|
||||
color = tinycolor(data.hsl);
|
||||
} else if (data && data.hex && data.hex.length > 0) {
|
||||
color = tinycolor(data.hex);
|
||||
} else if (data && data.hsv) {
|
||||
color = tinycolor(data.hsv);
|
||||
} else if (data && data.rgba) {
|
||||
color = tinycolor(data.rgba);
|
||||
} else if (data && data.rgb) {
|
||||
color = tinycolor(data.rgb);
|
||||
} else {
|
||||
color = tinycolor(data);
|
||||
}
|
||||
|
||||
if (color && (color._a === undefined || color._a === null)) {
|
||||
color.setAlpha(alpha || 1);
|
||||
}
|
||||
|
||||
const hsl = color.toHsl();
|
||||
const hsv = color.toHsv();
|
||||
|
||||
if (hsl.s === 0) {
|
||||
hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0;
|
||||
}
|
||||
|
||||
/* --- comment this block to fix #109, may cause #25 again --- */
|
||||
// when the hsv.v is less than 0.0164 (base on test)
|
||||
// because of possible loss of precision
|
||||
// the result of hue and saturation would be miscalculated
|
||||
// if (hsv.v < 0.0164) {
|
||||
// hsv.h = data.h || (data.hsv && data.hsv.h) || 0
|
||||
// hsv.s = data.s || (data.hsv && data.hsv.s) || 0
|
||||
// }
|
||||
|
||||
// if (hsl.l < 0.01) {
|
||||
// hsl.h = data.h || (data.hsl && data.hsl.h) || 0
|
||||
// hsl.s = data.s || (data.hsl && data.hsl.s) || 0
|
||||
// }
|
||||
/* ------ */
|
||||
|
||||
return {
|
||||
hsl: hsl,
|
||||
hex: color.toHexString().toUpperCase(),
|
||||
hex8: color.toHex8String().toUpperCase(),
|
||||
rgba: color.toRgb(),
|
||||
hsv: hsv,
|
||||
oldHue: data.h || oldHue || hsl.h,
|
||||
source: data.source,
|
||||
a: data.a || color.getAlpha(),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {
|
||||
val: _colorChange(this.value),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
colors: {
|
||||
get() {
|
||||
return this.val;
|
||||
},
|
||||
set(newVal) {
|
||||
this.val = newVal;
|
||||
this.$emit('update:value', newVal);
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.val = _colorChange(newVal);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
colorChange(data, oldHue) {
|
||||
this.oldHue = this.colors.hsl.h;
|
||||
this.colors = _colorChange(data, oldHue || this.oldHue);
|
||||
},
|
||||
isValidHex(hex) {
|
||||
return tinycolor(hex).isValid();
|
||||
},
|
||||
simpleCheckForValidColor(data) {
|
||||
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
|
||||
let checked = 0;
|
||||
let passed = 0;
|
||||
|
||||
for (let i = 0; i < keysToCheck.length; i++) {
|
||||
const letter = keysToCheck[i];
|
||||
if (data[letter]) {
|
||||
checked++;
|
||||
if (!isNaN(data[letter])) {
|
||||
passed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checked === passed) {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
paletteUpperCase(palette) {
|
||||
return palette.map((c) => c.toUpperCase());
|
||||
},
|
||||
isTransparent(color) {
|
||||
return tinycolor(color).getAlpha() === 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user