diff --git a/examples/routers/color-picker.vue b/examples/routers/color-picker.vue index 589e55e..6e4ca06 100644 --- a/examples/routers/color-picker.vue +++ b/examples/routers/color-picker.vue @@ -1,46 +1,157 @@ + diff --git a/src/components/color-picker/alpha.vue b/src/components/color-picker/alpha.vue index 6c77cba..32ef6cc 100644 --- a/src/components/color-picker/alpha.vue +++ b/src/components/color-picker/alpha.vue @@ -1,77 +1,103 @@ + \ No newline at end of file + + const {clientWidth} = this.$refs.container; + + if (left > clientWidth) { + this.change(1); + return; + } + + this.change(Math.round(left * 100 / clientWidth) / 100); + }, + }, +}; + diff --git a/src/components/color-picker/color-picker.vue b/src/components/color-picker/color-picker.vue index 3426900..0f4e9bc 100644 --- a/src/components/color-picker/color-picker.vue +++ b/src/components/color-picker/color-picker.vue @@ -1,375 +1,453 @@ - diff --git a/src/components/color-picker/handleEscapeMixin.js b/src/components/color-picker/handleEscapeMixin.js new file mode 100644 index 0000000..109f7b1 --- /dev/null +++ b/src/components/color-picker/handleEscapeMixin.js @@ -0,0 +1,7 @@ +export default { + methods: { + handleEscape(e) { + this.dispatch('ColorPicker', 'on-escape-keydown', e); + }, + }, +}; diff --git a/src/components/color-picker/hsaMixin.js b/src/components/color-picker/hsaMixin.js new file mode 100644 index 0000000..3f904d0 --- /dev/null +++ b/src/components/color-picker/hsaMixin.js @@ -0,0 +1,73 @@ +import Emitter from '../../mixins/emitter'; +import handleEscapeMixin from './handleEscapeMixin'; +import {getTouches} from './utils'; + +export default { + mixins: [Emitter, handleEscapeMixin], + + props: { + focused: { + type: Boolean, + default: false, + }, + value: { + type: Object, + default: undefined, + }, + }, + + beforeDestroy() { + this.unbindEventListeners(); + }, + + created() { + if (this.focused) { + setTimeout(() => this.$el.focus(), 1); + } + }, + + methods: { + handleLeft(e) { + this.handleSlide(e, this.left, 'left'); + }, + handleRight(e) { + this.handleSlide(e, this.right, 'right'); + }, + handleUp(e) { + this.handleSlide(e, this.up, 'up'); + }, + handleDown(e) { + this.handleSlide(e, this.down, 'down'); + }, + handleMouseDown(e) { + this.dispatch('ColorPicker', 'on-dragging', true); + this.handleChange(e, true); + window.addEventListener('mousemove', this.handleChange, false); + window.addEventListener('mouseup', this.handleMouseUp, false); + }, + handleMouseUp() { + this.unbindEventListeners(); + }, + unbindEventListeners() { + window.removeEventListener('mousemove', this.handleChange); + window.removeEventListener('mouseup', this.handleMouseUp); + // This timeout is required so that the click handler for click-outside + // has the chance to run before the mouseup removes the dragging flag. + setTimeout(() => this.dispatch('ColorPicker', 'on-dragging', false), 1); + }, + getLeft(e) { + const {container} = this.$refs; + const xOffset = container.getBoundingClientRect().left + window.pageXOffset; + const pageX = e.pageX || getTouches(e, 'PageX'); + + return pageX - xOffset; + }, + getTop(e) { + const {container} = this.$refs; + const yOffset = container.getBoundingClientRect().top + window.pageYOffset; + const pageY = e.pageY || getTouches(e, 'PageY'); + + return pageY - yOffset; + }, + }, +}; diff --git a/src/components/color-picker/hue.vue b/src/components/color-picker/hue.vue index ee9812a..b08c7b7 100644 --- a/src/components/color-picker/hue.vue +++ b/src/components/color-picker/hue.vue @@ -1,86 +1,95 @@ + \ No newline at end of file + + this.change(left * 100 / clientWidth); + }, + }, +}; + diff --git a/src/components/color-picker/index.js b/src/components/color-picker/index.js index f573739..f6e20a6 100644 --- a/src/components/color-picker/index.js +++ b/src/components/color-picker/index.js @@ -1,2 +1,3 @@ import ColorPicker from './color-picker.vue'; -export default ColorPicker; \ No newline at end of file + +export default ColorPicker; diff --git a/src/components/color-picker/prefixMixin.js b/src/components/color-picker/prefixMixin.js new file mode 100644 index 0000000..94668a6 --- /dev/null +++ b/src/components/color-picker/prefixMixin.js @@ -0,0 +1,10 @@ +export default { + data() { + return { + prefixCls: 'ivu-color-picker', + inputPrefixCls: 'ivu-input', + iconPrefixCls: 'ivu-icon', + transferPrefixCls: 'ivu-transfer', + }; + }, +}; diff --git a/src/components/color-picker/recommend-colors.vue b/src/components/color-picker/recommend-colors.vue index 8955ce0..311d859 100644 --- a/src/components/color-picker/recommend-colors.vue +++ b/src/components/color-picker/recommend-colors.vue @@ -1,20 +1,153 @@ + \ No newline at end of file + }, + lineBreak(list, index) { + if (!index) { + return false; + } + + const nextIndex = index + 1; + + return nextIndex < list.length && nextIndex % this.columns === 0; + }, + }, +}; + diff --git a/src/components/color-picker/saturation.vue b/src/components/color-picker/saturation.vue index a6e11f4..a36bf96 100644 --- a/src/components/color-picker/saturation.vue +++ b/src/components/color-picker/saturation.vue @@ -1,98 +1,98 @@ + \ No newline at end of file + }, +}; + diff --git a/src/components/color-picker/utils.js b/src/components/color-picker/utils.js new file mode 100644 index 0000000..37f3f51 --- /dev/null +++ b/src/components/color-picker/utils.js @@ -0,0 +1,118 @@ +import tinycolor from 'tinycolor2'; +import {oneOf} from '../../utils/assist'; + +function setAlpha(data, alpha) { + const color = tinycolor(data); + const {_a} = color; + + if (_a === undefined || _a === null) { + color.setAlpha(alpha || 1); + } + + return color; +} + +function getColor(data, colorData) { + const alpha = colorData && colorData.a; + + if (colorData) { + // hsl is better than hex between conversions + if (colorData.hsl) { + return setAlpha(colorData.hsl, alpha); + } + + if (colorData.hex && colorData.hex.length > 0) { + return setAlpha(colorData.hex, alpha); + } + } + + return setAlpha(colorData, alpha); +} + +export function changeColor(data, oldHue) { + const colorData = data === '' ? '#2d8cf0' : data; + const color = getColor(data, colorData); + const hsl = color.toHsl(); + const hsv = color.toHsv(); + + if (hsl.s === 0) { + hsl.h = colorData.h || (colorData.hsl && colorData.hsl.h) || oldHue || 0; + hsv.h = hsl.h; + } + + // 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 = colorData.h || (colorData.hsv && colorData.hsv.h) || 0; + hsv.s = colorData.s || (colorData.hsv && colorData.hsv.s) || 0; + } + + if (hsl.l < 0.01) { + hsl.h = colorData.h || (colorData.hsl && colorData.hsl.h) || 0; + hsl.s = colorData.s || (colorData.hsl && colorData.hsl.s) || 0; + } + + return { + hsl, + hex: color.toHexString().toUpperCase(), + rgba: color.toRgb(), + hsv, + oldHue: colorData.h || oldHue || hsl.h, + source: colorData.source, + a: colorData.a || color.getAlpha(), + }; +} + +export function clamp(value, min, max) { + if (value < min) { + return min; + } + + if (value > max) { + return max; + } + + return value; +} + +export function getIncrement(key, keys, increment) { + return oneOf(key, keys) ? increment : 0; +} + +export function getTouches(e, prop) { + return e.touches ? e.touches[0][prop] : 0; +} + +export function toRGBAString(rgba) { + const {r, g, b, a} = rgba; + + return `rgba(${[r, g, b, a].join(',')})`; +} + +export function isValidHex(hex) { + return tinycolor(hex).isValid(); +} + +function checkIteratee(data, counts, letter) { + let {checked, passed} = counts; + const value = data[letter]; + + if (value) { + checked += 1; + + if (Number.isFinite(value)) { + passed += 1; + } + } + + return {checked, passed}; +} + +const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v']; + +export function simpleCheckForValidColor(data) { + const results = keysToCheck.reduce(checkIteratee.bind(null, data), {checked: 0, passed: 0}); + + return results.checked === results.passed ? data : undefined; +} diff --git a/src/styles/components/color-picker.less b/src/styles/components/color-picker.less index 98b7c3b..59ee907 100644 --- a/src/styles/components/color-picker.less +++ b/src/styles/components/color-picker.less @@ -1,103 +1,174 @@ -@color-picker-prefix-cls: ~"@{css-prefix}color-picker"; +@color-picker-prefix-cls: ~'@{css-prefix}color-picker'; .@{color-picker-prefix-cls} { display: inline-block; + &-hide { + display: none; + &-drop { + visibility: hidden; + } + } + &-disabled { + background-color: @input-disabled-bg; + opacity: 1; + cursor: @cursor-disabled; + color: #ccc; + } + & > div:first-child:hover { + .ivu-input { + border-color: @input-hover-border-color; + } + } + & > div:first-child.@{color-picker-prefix-cls}-disabled:hover { + .ivu-input { + border-color: tint(@input-border-color, 20%); + } + } & .@{select-dropdown-prefix-cls} { padding: 0; } - &-rel{ + &-focused { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } + &-rel { line-height: 0; } - &-color{ + &-color { width: 18px; height: 18px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); border-radius: 2px; position: relative; top: 2px; - div{ + div { width: 100%; height: 100%; - box-shadow: inset 0 0 0 1px rgba(0,0,0,.15); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15); border-radius: 2px; } - &-empty{ + &-empty { background: #fff; overflow: hidden; text-align: center; - i{ + i { font-size: 18px; } } + &-focused { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } } - &-large &-color{ + &-large &-color { width: 20px; height: 20px; top: 1px; - &-empty{ - i{ + &-empty { + i { font-size: 20px; } } } - &-small &-color{ + &-small &-color { width: 14px; height: 14px; top: 3px; - &-empty{ - i{ + &-empty { + i { font-size: 14px; } } } - &-picker{ - &-wrapper{ + &-picker { + &-wrapper { padding: 8px 8px 0; } - &-panel{ + &-panel { width: 240px; margin: 0 auto; box-sizing: initial; position: relative; } - &-hue-slider, &-alpha-slider{ + &-hue-slider, + &-alpha-slider { height: 10px; margin-top: 8px; position: relative; } - &-colors{ + &-colors { margin-top: 8px; overflow: hidden; - span{ - display: inline-block; + outline: 0; + border: 1px solid @input-border-color; + transition: border @transition-time @ease-in-out, box-shadow @transition-time @ease-in-out; + &:hover { + border: 1px solid @input-hover-border-color; + } + &:focus { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } + &-wrapper { + display: inline; width: 20px; height: 20px; float: left; - em{ + position: relative; + &-color { + outline: 0; display: block; + position: absolute; width: 16px; height: 16px; margin: 2px; cursor: pointer; border-radius: 2px; - box-shadow: inset 0 0 0 1px rgba(0,0,0,.15); + border: 1px solid @input-border-color; + transition: border @transition-time @ease-in-out, box-shadow @transition-time @ease-in-out; + &:hover { + border: 1px solid @input-hover-border-color; + } + &:focus { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } + } + &-circle { + cursor: pointer; + top: 10px; + left: 10px; + position: absolute; + 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); } } } - .@{picker-prefix-cls}-confirm{ + .@{picker-prefix-cls}-confirm { margin-top: 8px; } } - &-saturation{ - &-wrapper{ + &-saturation { + &-wrapper { width: 100%; padding-bottom: 75%; position: relative; overflow: hidden; + outline: 0; + border: 1px solid @input-border-color; + box-shadow: @shadow-base; + transition: border @transition-time @ease-in-out, box-shadow @transition-time @ease-in-out; + &:hover { + border: 1px solid @input-hover-border-color; + } + &:focus { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } } - &, &--white, &--black{ + &, + &--white, + &--black { cursor: pointer; position: absolute; top: 0; @@ -105,26 +176,26 @@ right: 0; bottom: 0; } - &--white{ - background: linear-gradient(to right, #fff, rgba(255,255,255,0)); + &--white { + background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); } - &--black{ - background: linear-gradient(to top, #000, rgba(0,0,0,0)); + &--black { + background: linear-gradient(to top, #000, rgba(0, 0, 0, 0)); } - &-pointer{ + &-pointer { cursor: pointer; position: absolute; } - &-circle{ + &-circle { width: 4px; height: 4px; - box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0,0,0,.3), 0 0 1px 2px rgba(0,0,0,.4); + 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); } } - &-hue{ + &-hue { position: absolute; top: 0; right: 0; @@ -132,35 +203,55 @@ left: 0; border-radius: 2px; background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); - &-container{ + outline: 0; + border: 1px solid @input-border-color; + box-shadow: @shadow-base; + transition: border @transition-time @ease-in-out, box-shadow @transition-time @ease-in-out; + &:hover { + border: 1px solid @input-hover-border-color; + } + &:focus { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } + &-container { cursor: pointer; margin: 0 2px; position: relative; height: 100%; } - &-pointer{ + &-pointer { z-index: 2; position: absolute; } - &-picker{ + &-picker { cursor: pointer; margin-top: 1px; width: 4px; border-radius: 1px; height: 8px; - box-shadow: 0 0 2px rgba(0, 0, 0, .6); + box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); background: #fff; transform: translateX(-2px); } } - &-alpha{ + &-alpha { position: absolute; top: 0; right: 0; bottom: 0; left: 0; - &-checkboard-wrap{ + outline: 0; + border: 1px solid @input-border-color; + box-shadow: @shadow-base; + transition: border @transition-time @ease-in-out, box-shadow @transition-time @ease-in-out; + &:hover { + border: 1px solid @input-hover-border-color; + } + &:focus { + box-shadow: 0 0 0 2px fade(@input-hover-border-color, 20%); + } + &-checkboard-wrap { position: absolute; top: 0; right: 0; @@ -169,7 +260,7 @@ overflow: hidden; border-radius: 2px; } - &-checkerboard{ + &-checkerboard { position: absolute; top: 0; right: 0; @@ -177,7 +268,7 @@ left: 0; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); } - &-gradient{ + &-gradient { position: absolute; top: 0; right: 0; @@ -185,32 +276,37 @@ left: 0; border-radius: 2px; } - &-container{ + &-container { cursor: pointer; position: relative; z-index: 2; height: 100%; margin: 0 3px; } - &-pointer{ + &-pointer { z-index: 2; position: absolute; } - &-picker{ + &-picker { cursor: pointer; width: 4px; border-radius: 1px; height: 8px; - box-shadow: 0 0 2px rgba(0, 0, 0, .6); + box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); background: #fff; margin-top: 1px; transform: translateX(-2px); } } - &-confirm{ + &-confirm { + margin-top: 8px; position: relative; - &-color{ + border-top: 1px solid @border-color-split; + text-align: right; + padding: 8px; + clear: both; + &-color { position: absolute; top: 11px; left: 8px; diff --git a/src/styles/mixins/input.less b/src/styles/mixins/input.less index 8c8995a..136f235 100644 --- a/src/styles/mixins/input.less +++ b/src/styles/mixins/input.less @@ -6,7 +6,6 @@ } .active(@color: @input-hover-border-color) { - border-color: tint(@color, 20%); outline: 0; box-shadow: 0 0 0 2px fade(@color, 20%); } @@ -266,4 +265,4 @@ &-append { border-left: 0; } -} \ No newline at end of file +} -- libgit2 0.21.4