vuetify
Version:
Vue Material Component Framework
188 lines (187 loc) • 5.98 kB
JavaScript
import { createVNode as _createVNode } from "vue";
// Styles
import "./VColorPickerCanvas.css";
// Composables
import { makeComponentProps } from "../../composables/component.js";
import { useResizeObserver } from "../../composables/resizeObserver.js"; // Utilities
import { computed, onMounted, ref, shallowRef, watch } from 'vue';
import { clamp, convertToUnit, defineComponent, getEventCoordinates, propsFactory, useRender } from "../../util/index.js"; // Types
export const makeVColorPickerCanvasProps = propsFactory({
color: {
type: Object
},
disabled: Boolean,
dotSize: {
type: [Number, String],
default: 10
},
height: {
type: [Number, String],
default: 150
},
width: {
type: [Number, String],
default: 300
},
...makeComponentProps()
}, 'VColorPickerCanvas');
export const VColorPickerCanvas = defineComponent({
name: 'VColorPickerCanvas',
props: makeVColorPickerCanvasProps(),
emits: {
'update:color': color => true,
'update:position': hue => true
},
setup(props, _ref) {
let {
emit
} = _ref;
const isInteracting = shallowRef(false);
const canvasRef = ref();
const canvasWidth = shallowRef(parseFloat(props.width));
const canvasHeight = shallowRef(parseFloat(props.height));
const _dotPosition = ref({
x: 0,
y: 0
});
const dotPosition = computed({
get: () => _dotPosition.value,
set(val) {
if (!canvasRef.value) return;
const {
x,
y
} = val;
_dotPosition.value = val;
emit('update:color', {
h: props.color?.h ?? 0,
s: clamp(x, 0, canvasWidth.value) / canvasWidth.value,
v: 1 - clamp(y, 0, canvasHeight.value) / canvasHeight.value,
a: props.color?.a ?? 1
});
}
});
const dotStyles = computed(() => {
const {
x,
y
} = dotPosition.value;
const radius = parseInt(props.dotSize, 10) / 2;
return {
width: convertToUnit(props.dotSize),
height: convertToUnit(props.dotSize),
transform: `translate(${convertToUnit(x - radius)}, ${convertToUnit(y - radius)})`
};
});
const {
resizeRef
} = useResizeObserver(entries => {
if (!resizeRef.el?.offsetParent) return;
const {
width,
height
} = entries[0].contentRect;
canvasWidth.value = width;
canvasHeight.value = height;
});
function updateDotPosition(x, y, rect) {
const {
left,
top,
width,
height
} = rect;
dotPosition.value = {
x: clamp(x - left, 0, width),
y: clamp(y - top, 0, height)
};
}
function handleMouseDown(e) {
if (e.type === 'mousedown') {
// Prevent text selection while dragging
e.preventDefault();
}
if (props.disabled) return;
handleMouseMove(e);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
window.addEventListener('touchmove', handleMouseMove);
window.addEventListener('touchend', handleMouseUp);
}
function handleMouseMove(e) {
if (props.disabled || !canvasRef.value) return;
isInteracting.value = true;
const coords = getEventCoordinates(e);
updateDotPosition(coords.clientX, coords.clientY, canvasRef.value.getBoundingClientRect());
}
function handleMouseUp() {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
window.removeEventListener('touchmove', handleMouseMove);
window.removeEventListener('touchend', handleMouseUp);
}
function updateCanvas() {
if (!canvasRef.value) return;
const canvas = canvasRef.value;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const saturationGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
saturationGradient.addColorStop(0, 'hsla(0, 0%, 100%, 1)'); // white
saturationGradient.addColorStop(1, `hsla(${props.color?.h ?? 0}, 100%, 50%, 1)`);
ctx.fillStyle = saturationGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
const valueGradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
valueGradient.addColorStop(0, 'hsla(0, 0%, 0%, 0)'); // transparent
valueGradient.addColorStop(1, 'hsla(0, 0%, 0%, 1)'); // black
ctx.fillStyle = valueGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
watch(() => props.color?.h, updateCanvas, {
immediate: true
});
watch(() => [canvasWidth.value, canvasHeight.value], (newVal, oldVal) => {
updateCanvas();
_dotPosition.value = {
x: dotPosition.value.x * newVal[0] / oldVal[0],
y: dotPosition.value.y * newVal[1] / oldVal[1]
};
}, {
flush: 'post'
});
watch(() => props.color, () => {
if (isInteracting.value) {
isInteracting.value = false;
return;
}
_dotPosition.value = props.color ? {
x: props.color.s * canvasWidth.value,
y: (1 - props.color.v) * canvasHeight.value
} : {
x: 0,
y: 0
};
}, {
deep: true,
immediate: true
});
onMounted(() => updateCanvas());
useRender(() => _createVNode("div", {
"ref": resizeRef,
"class": ['v-color-picker-canvas', props.class],
"style": props.style,
"onMousedown": handleMouseDown,
"onTouchstartPassive": handleMouseDown
}, [_createVNode("canvas", {
"ref": canvasRef,
"width": canvasWidth.value,
"height": canvasHeight.value
}, null), props.color && _createVNode("div", {
"class": ['v-color-picker-canvas__dot', {
'v-color-picker-canvas__dot--disabled': props.disabled
}],
"style": dotStyles.value
}, null)]));
return {};
}
});
//# sourceMappingURL=VColorPickerCanvas.js.map