vuetify
Version:
Vue Material Component Framework
192 lines (189 loc) • 5.96 kB
JavaScript
import { createVNode as _createVNode } from "vue";
// Styles
import "./VColorPickerCanvas.css";
// Composables
import { useResizeObserver } from "../../composables/resizeObserver.mjs"; // Utilities
import { clamp, convertToUnit, defineComponent, getEventCoordinates, useRender } from "../../util/index.mjs";
import { computed, onMounted, ref, watch } from 'vue';
// Types
export const VColorPickerCanvas = defineComponent({
name: 'VColorPickerCanvas',
props: {
color: {
type: Object
},
disabled: Boolean,
dotSize: {
type: [Number, String],
default: 10
},
height: {
type: [Number, String],
default: 150
},
width: {
type: [Number, String],
default: 300
}
},
emits: {
'update:color': color => true,
'update:position': hue => true
},
setup(props, _ref) {
let {
emit
} = _ref;
const isInteracting = ref(false);
const isOutsideUpdate = ref(false);
const dotPosition = ref({
x: 0,
y: 0
});
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 canvasRef = ref();
const canvasWidth = ref(parseFloat(props.width));
const canvasHeight = ref(parseFloat(props.height));
const {
resizeRef
} = useResizeObserver(entries => {
if (!resizeRef.value?.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 handleClick(e) {
if (props.disabled || !canvasRef.value) return;
updateDotPosition(e.clientX, e.clientY, canvasRef.value.getBoundingClientRect());
}
function handleMouseDown(e) {
// To prevent selection while moving cursor
e.preventDefault();
if (props.disabled) return;
isInteracting.value = true;
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);
}
watch(dotPosition, () => {
if (isOutsideUpdate.value) {
isOutsideUpdate.value = false;
return;
}
if (!canvasRef.value) return;
const {
x,
y
} = dotPosition.value;
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
});
});
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%, 100%, 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;
}
isOutsideUpdate.value = true;
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",
"onClick": handleClick,
"onMousedown": handleMouseDown,
"onTouchstart": 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.mjs.map