@teammodlr/ui-kit 2.3.6 → 2.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,438 +1,450 @@
1
- <template>
2
- <div ref="wrapper" class="m-color-picker">
3
- <div ref="referenceRef" @click="isOpen = !isOpen">
4
- <slot :r="finalColor.r" :g="finalColor.g" :b="finalColor.b" :a="finalColor.a" :hex="hexInput"
5
- :transparent="style.transparent">
6
- <div>
7
- <template v-if="modelValue === 'inherit'">
8
- <div :style="{ border: '1px dashed #999', padding: '8px', textAlign: 'center' }">Inherit</div>
9
- </template>
10
- <template v-else>
11
- <!-- Regular color display -->
12
- <div :class="style.referenceColor"
13
- :style="{ backgroundColor: `rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, ${finalColor.a})` }">
14
- </div>
15
- </template>
16
- </div>
17
- </slot>
18
- </div>
19
- <Teleport to="body">
20
- <transition :enter-active-class="scale.enterActive" :leave-active-class="scale.leaveActive"
21
- :enter-from-class="scale.enterFrom" :leave-to-class="scale.leaveTo">
22
- <div :class="style.container" v-if="isOpen" ref="popper" :style="{ zIndex: zIndex }">
23
- <div ref="picker" @mousedown="handleMouseDown" :class="style.colours"
24
- :style="`background: rgb(${mainColor.r}, ${mainColor.g}, ${mainColor.b});`">
25
- <div :class="style.whiteGradient"></div>
26
- <div :class="style.blackGradient"></div>
27
- <div :class="style.sliderThumb" @mousedown="handleMouseDown" @dragover.prevent=""
28
- :style="{ left: `${position.x * 100}%`, top: `${position.y * 100}%` }"></div>
29
- </div>
30
-
31
- <div :class="style.colorActions">
32
- <template v-if="canPickColor">
33
- <MButton icon :disabled="!canPickColor" :class="style.eyeDrop" @click="openEyeDropper">
34
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
35
- xmlns="http://www.w3.org/2000/svg">
36
- <path
37
- d="M20.3847 2.87868C19.2132 1.70711 17.3137 1.70711 16.1421 2.87868L14.0202 5.00052L13.313 4.29332C12.9225 3.9028 12.2894 3.9028 11.8988 4.29332C11.5083 4.68385 11.5083 5.31701 11.8988 5.70754L17.5557 11.3644C17.9462 11.7549 18.5794 11.7549 18.9699 11.3644C19.3604 10.9739 19.3604 10.3407 18.9699 9.95018L18.2629 9.24316L20.3847 7.12132C21.5563 5.94975 21.5563 4.05025 20.3847 2.87868Z"
38
- fill="currentColor" />
39
- <path fill-rule="evenodd" clip-rule="evenodd"
40
- d="M11.9297 7.09116L4.1515 14.8693C3.22786 15.793 3.03239 17.169 3.5651 18.2842L1.99994 19.8493L3.41415 21.2635L4.97931 19.6984C6.09444 20.2311 7.4705 20.0356 8.39414 19.112L16.1723 11.3338L11.9297 7.09116ZM13.3439 11.3338L11.9297 9.91959L5.56571 16.2835C5.17518 16.6741 5.17518 17.3072 5.56571 17.6978C5.95623 18.0883 6.5894 18.0883 6.97992 17.6978L13.3439 11.3338Z"
41
- fill="currentColor" />
42
- </svg>
43
- </MButton>
44
- </template>
45
- <template v-else>
46
- <div :class="style.preview">
47
- <div
48
- :style="`width: 100%; height: 100%; background-color: rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, ${finalColor.a});`">
49
- </div>
50
- </div>
51
- </template>
52
- <div style="width: 100%; display: flex; flex-direction: column; gap: 10px;">
53
- <MSlider v-model="hueSlider" @change="onSliderChangeComplete"
54
- background="linear-gradient(to right, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)">
55
- </MSlider>
56
-
57
- <MSlider v-model="opacitySlider" @change="onSliderChangeComplete"
58
- :background="`linear-gradient(to right, rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, 0), rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, 1))`">
59
- </MSlider>
60
- </div>
61
- </div>
62
-
63
- <div :class="style.colorActions">
64
- <template v-if="rgbOrHex">
65
- <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="rInputElement"
66
- v-model="rInput" name="r" placeholder="R" />
67
- <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="gInputElement"
68
- v-model="gInput" name="g" placeholder="G" />
69
- <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="bInputElement"
70
- v-model="bInput" name="b" placeholder="B" />
71
- <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="aInputElement"
72
- v-model="aInput" name="a" placeholder="A" />
73
- </template>
74
- <template v-else>
75
- <MInput
76
- @keyup.enter="commitHexInput"
77
- @blur="commitHexInput"
78
- v-model="hexInputUser"
79
- block
80
- ref="hexInputElement"
81
- name="hex"
82
- placeholder="#00000000"
83
- />
84
- </template>
85
-
86
- <MButton icon @click.prevent="rgbOrHex = !rgbOrHex">
87
-
88
- <svg style="width: 18px; height: 18px;" xmlns="http://www.w3.org/2000/svg"
89
- viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
90
- <path fill-rule="evenodd"
91
- d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
92
- clip-rule="evenodd" />
93
- </svg>
94
-
95
- </MButton>
96
- <MButton icon @click.prevent="toggleInherit">
97
- <svg style="width: 18px; height: 18px;" xmlns="http://www.w3.org/2000/svg" width="24"
98
- height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
99
- stroke-linecap="round" stroke-linejoin="round">
100
- <path d="M21 6H3" />
101
- <path d="M7 12H3" />
102
- <path d="M7 18H3" />
103
- <path d="M12 18a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L11 14" />
104
- <path d="M11 10v4h4" />
105
- </svg>
106
- </MButton>
107
- </div>
108
- </div>
109
- </transition>
110
- </Teleport>
111
- </div>
112
- </template>
113
-
114
- <script lang="ts">
115
- import { onClickOutside, useEyeDropper, useMouseInElement, useMousePressed } from '@vueuse/core';
116
- import { computed, defineComponent, onMounted, type PropType, reactive, ref, toRef, watch } from "vue";
117
- import * as style from './style';
118
- import { scale } from "../../styles/transitions.css";
119
- import { hexToRGB, type HSV, HSVToRGBA, HueToRGBA, RGBAToHex, RGBToHSV } from './utils';
120
- import { usePopper } from "../../composables/usePopper";
121
- import type { Placement } from "@popperjs/core";
122
- import { useZIndex } from "../../composables/useZIndex";
123
- import MSlider from "../../components/slider/Slider.vue";
124
- import MButton from "../../components/button/Button.vue";
125
- import MInput from "../../components/input/Input.vue";
126
-
127
- export default defineComponent({
128
- name: "MColorPicker",
129
- props: {
130
- modelValue: {
131
- type: String,
132
- default: "#FFFFFF"
133
- },
134
- modelModifiers: {
135
- type: Object,
136
- default: () => ({})
137
- },
138
- transfer: {
139
- type: Boolean,
140
- default: false
141
- },
142
- placement: {
143
- type: String as PropType<Placement>,
144
- default: "bottom-start"
145
- },
146
- zIndex: {
147
- type: Number
148
- },
149
- defaultColor: {
150
- type: String,
151
- default: "#FFFFFF"
152
- }
153
- },
154
- emits: ['update:modelValue'],
155
- components: {
156
- MSlider,
157
- MInput,
158
- MButton
159
- },
160
- setup(props, { emit }) {
161
- const modelModifiers = props.modelModifiers;
162
- const { isSupported: canPickColor, open: openEyeDrop, sRGBHex: eyeDropColor } = useEyeDropper();
163
- const { nextZIndex } = useZIndex();
164
- const isOpen = ref(false);
165
- const rgbOrHex = ref<boolean>(false);
166
- const hexInput = ref("");
167
- const rInput = ref("");
168
- const gInput = ref("");
169
- const bInput = ref("");
170
- const aInput = ref("");
171
- const internalValue = ref(props.modelValue);
172
-
173
- const picker = ref<HTMLElement>();
174
- const wrapper = ref<HTMLElement>();
175
- const referenceRef = ref<HTMLElement>();
176
- const transfer = toRef(props, 'transfer');
177
- const placement = toRef(props, 'placement');
178
- const hexInputElement = ref();
179
- const hueSlider = ref(0);
180
- const opacitySlider = ref(100);
181
- const dragging = ref(false);
182
- const position = reactive({
183
- x: 0,
184
- y: 0
185
- });
186
- const zIndex = ref(props.zIndex ?? nextZIndex());
187
-
188
- const { elementX, elementY } = useMouseInElement(picker);
189
- const { pressed } = useMousePressed();
190
- const { popper, transferTo, updatePopper } = usePopper({
191
- reference: referenceRef,
192
- placement,
193
- transfer,
194
- wrapper,
195
- isDrop: false,
196
- offset: [-2, 2]
197
- });
198
- const brightness = computed(() => {
199
- return 1 - position.y;
200
- })
201
-
202
- const saturation = computed(() => {
203
- return position.x;
204
- })
205
-
206
- const finalColor = computed(() => {
207
- if (hexInput.value === "inherit") {
208
- return { r: 0, g: 0, b: 0, a: 0 };
209
- }
210
- return HSVToRGBA(hueSlider.value * 3.6, brightness.value, saturation.value, opacitySlider.value / 100);
211
- });
212
-
213
- const mainColor = computed(() => {
214
- return HueToRGBA(hueSlider.value * 3.6, opacitySlider.value / 100);
215
- });
216
-
217
- const emitModelValue = () => {
218
- emit('update:modelValue', hexInput.value);
219
- };
220
-
221
- const setInputs = () => {
222
- if (hexInput.value !== 'inherit') {
223
- hexInput.value = RGBAToHex(finalColor.value.r, finalColor.value.g, finalColor.value.b, finalColor.value.a);
224
- }
225
- rInput.value = finalColor.value.r.toString();
226
- gInput.value = finalColor.value.g.toString();
227
- bInput.value = finalColor.value.b.toString();
228
- aInput.value = finalColor.value.a.toFixed(2).toString();
229
-
230
- if (!modelModifiers.lazy) {
231
- emitModelValue();
232
- }
233
- };
234
-
235
- watch(() => finalColor.value, () => {
236
- setInputs();
237
- });
238
-
239
- const updateFromHSV = (hsv: HSV, alpha: number) => {
240
- position.x = hsv.s;
241
- position.y = 1 - hsv.v;
242
- hueSlider.value = ((1 / 360) * hsv.h) * 100;
243
- opacitySlider.value = alpha * 100;
244
- setInputs();
245
- }
246
-
247
- const updateFromHex = () => {
248
- if (hexInput.value === 'inherit') {
249
- if (modelModifiers.lazy) {
250
- emitModelValue();
251
- }
252
- return;
253
- }
254
- const color = hexToRGB(hexInput.value);
255
- if (color.a === 0) {
256
- color.a = 1;
257
- opacitySlider.value = 100;
258
- }
259
- updateFromHSV(RGBToHSV(color.r, color.g, color.b), color.a);
260
- if (modelModifiers.lazy) {
261
- emitModelValue();
262
- }
263
- };
264
-
265
- const toggleInherit = () => {
266
- if (props.modelValue === "inherit") {
267
- hexInput.value = "#FFFFFF";
268
- } else {
269
- hexInput.value = "inherit";
270
- }
271
- emitModelValue();
272
- };
273
-
274
- const updateFromRGB = () => {
275
- let red = parseInt(rInput.value);
276
- if (isNaN(red) || red > 255 || red < 0) {
277
- red = finalColor.value.r;
278
- }
279
-
280
- let green = parseInt(gInput.value);
281
- if (isNaN(green) || green > 255 || green < 0) {
282
- green = finalColor.value.g;
283
- }
284
-
285
- let blue = parseInt(bInput.value);
286
- if (isNaN(blue) || blue > 255 || blue < 0) {
287
- blue = finalColor.value.b;
288
- }
289
-
290
- let alpha = parseFloat(aInput.value);
291
- if (isNaN(alpha) || alpha > 1 || alpha < 0) {
292
- alpha = finalColor.value.a;
293
- }
294
-
295
- updateFromHSV(RGBToHSV(red, green, blue), alpha);
296
-
297
- if (hexInput.value === 'inherit') {
298
- hexInput.value = RGBAToHex(red, green, blue, alpha);
299
- emitModelValue();
300
- } else if (modelModifiers.lazy) {
301
- emitModelValue();
302
- }
303
- };
304
-
305
- watch(() => pressed.value, () => {
306
- if (!pressed.value) {
307
- dragging.value = false;
308
- }
309
- });
310
-
311
- watch(() => dragging.value, (newVal, oldVal) => {
312
- if (oldVal && !newVal && modelModifiers.lazy) {
313
- emitModelValue();
314
- }
315
- });
316
-
317
- watch(() => [elementX.value, elementY.value], () => {
318
- if (!dragging.value) {
319
- return;
320
- }
321
-
322
- setThumbPosition();
323
- });
324
-
325
- watch(() => props.modelValue, () => {
326
- let val = props.modelValue;
327
- if (!val || val.trim() === "") {
328
- val = props.defaultColor ?? "#FFFFFF";
329
- }
330
- hexInput.value = val;
331
- updateFromHex();
332
- });
333
-
334
- const setThumbPosition = () => {
335
- position.x = ((Math.min(Math.max(elementX.value, 0), picker.value?.clientWidth || 0) / (picker.value?.clientWidth || 0)));
336
- position.y = ((Math.min(Math.max(elementY.value, 0), picker.value?.clientHeight || 0) / (picker.value?.clientHeight || 0)));
337
- };
338
-
339
- const handleMouseDown = () => {
340
- if (hexInput.value === 'inherit') {
341
- hexInput.value = RGBAToHex(finalColor.value.r, finalColor.value.g, finalColor.value.b, finalColor.value.a);
342
- emitModelValue();
343
- }
344
- dragging.value = true;
345
- setThumbPosition();
346
- };
347
-
348
- onMounted(() => {
349
- hexInput.value = props.modelValue;
350
- updateFromHex();
351
- });
352
-
353
- onClickOutside(popper, (e) => {
354
- isOpen.value = false;
355
- });
356
-
357
-
358
- watch(() => eyeDropColor.value, () => {
359
- if (eyeDropColor.value) {
360
- hexInput.value = eyeDropColor.value;
361
- emitModelValue();
362
- updateFromHex();
363
- }
364
- });
365
-
366
- const openEyeDropper = () => {
367
- try {
368
- openEyeDrop();
369
- } catch (e) {
370
- console.log(e);
371
- }
372
- }
373
-
374
- const onSliderChangeComplete = () => {
375
- if (modelModifiers.lazy) {
376
- emitModelValue();
377
- }
378
- }
379
-
380
- const hexInputUser = ref(hexInput.value);
381
-
382
- watch(hexInput, (newVal) => {
383
- if (document.activeElement !== hexInputElement.value?.$el?.querySelector('input')) {
384
- hexInputUser.value = newVal;
385
- }
386
- });
387
-
388
- const commitHexInput = () => {
389
- if (hexInputUser.value.trim() === '') {
390
- hexInputUser.value = hexInput.value;
391
- return;
392
- }
393
-
394
- hexInput.value = hexInputUser.value;
395
- updateFromHex();
396
- };
397
-
398
-
399
- return {
400
- isOpen,
401
- popper,
402
- transferTo,
403
- wrapper,
404
- referenceRef,
405
- updateFromRGB,
406
- updateFromHex,
407
- setInputs,
408
- rgbOrHex,
409
- hexInputElement,
410
- hexInput,
411
- rInput,
412
- gInput,
413
- bInput,
414
- aInput,
415
- finalColor,
416
- hueSlider,
417
- opacitySlider,
418
- dragging,
419
- setThumbPosition,
420
- position,
421
- elementX,
422
- pressed,
423
- handleMouseDown,
424
- picker,
425
- style,
426
- mainColor,
427
- zIndex,
428
- canPickColor,
429
- openEyeDropper,
430
- scale,
431
- onSliderChangeComplete,
432
- hexInputUser,
433
- toggleInherit,
434
- commitHexInput
435
- };
436
- }
437
- })
438
- </script>
1
+ <template>
2
+ <div ref="wrapper" class="m-color-picker">
3
+ <div ref="referenceRef" @click="isOpen = !isOpen">
4
+ <slot :r="finalColor.r" :g="finalColor.g" :b="finalColor.b" :a="finalColor.a" :hex="hexInput"
5
+ :transparent="style.transparent">
6
+ <div>
7
+ <template v-if="modelValue === 'inherit'">
8
+ <div :style="{ border: '1px dashed #999', padding: '8px', textAlign: 'center' }">Inherit</div>
9
+ </template>
10
+ <template v-else>
11
+ <!-- Regular color display -->
12
+ <div :class="style.referenceColor"
13
+ :style="{ backgroundColor: `rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, ${finalColor.a})` }">
14
+ </div>
15
+ </template>
16
+ </div>
17
+ </slot>
18
+ </div>
19
+ <Teleport to="body">
20
+ <transition :enter-active-class="scale.enterActive" :leave-active-class="scale.leaveActive"
21
+ :enter-from-class="scale.enterFrom" :leave-to-class="scale.leaveTo">
22
+ <div :class="style.container" v-if="isOpen" ref="popper" :style="{ zIndex: zIndex }">
23
+ <div ref="picker" @mousedown="handleMouseDown" :class="style.colours"
24
+ :style="`background: rgb(${mainColor.r}, ${mainColor.g}, ${mainColor.b});`">
25
+ <div :class="style.whiteGradient"></div>
26
+ <div :class="style.blackGradient"></div>
27
+ <div :class="style.sliderThumb" @mousedown="handleMouseDown" @dragover.prevent=""
28
+ :style="{ left: `${position.x * 100}%`, top: `${position.y * 100}%` }"></div>
29
+ </div>
30
+
31
+ <div :class="style.colorActions">
32
+ <template v-if="canPickColor">
33
+ <MButton icon :disabled="!canPickColor" :class="style.eyeDrop" @click="openEyeDropper">
34
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
35
+ xmlns="http://www.w3.org/2000/svg">
36
+ <path
37
+ d="M20.3847 2.87868C19.2132 1.70711 17.3137 1.70711 16.1421 2.87868L14.0202 5.00052L13.313 4.29332C12.9225 3.9028 12.2894 3.9028 11.8988 4.29332C11.5083 4.68385 11.5083 5.31701 11.8988 5.70754L17.5557 11.3644C17.9462 11.7549 18.5794 11.7549 18.9699 11.3644C19.3604 10.9739 19.3604 10.3407 18.9699 9.95018L18.2629 9.24316L20.3847 7.12132C21.5563 5.94975 21.5563 4.05025 20.3847 2.87868Z"
38
+ fill="currentColor" />
39
+ <path fill-rule="evenodd" clip-rule="evenodd"
40
+ d="M11.9297 7.09116L4.1515 14.8693C3.22786 15.793 3.03239 17.169 3.5651 18.2842L1.99994 19.8493L3.41415 21.2635L4.97931 19.6984C6.09444 20.2311 7.4705 20.0356 8.39414 19.112L16.1723 11.3338L11.9297 7.09116ZM13.3439 11.3338L11.9297 9.91959L5.56571 16.2835C5.17518 16.6741 5.17518 17.3072 5.56571 17.6978C5.95623 18.0883 6.5894 18.0883 6.97992 17.6978L13.3439 11.3338Z"
41
+ fill="currentColor" />
42
+ </svg>
43
+ </MButton>
44
+ </template>
45
+ <template v-else>
46
+ <div :class="style.preview">
47
+ <div
48
+ :style="`width: 100%; height: 100%; background-color: rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, ${finalColor.a});`">
49
+ </div>
50
+ </div>
51
+ </template>
52
+ <div style="width: 100%; display: flex; flex-direction: column; gap: 10px;">
53
+ <MSlider v-model="hueSlider" @change="onSliderChangeComplete"
54
+ background="linear-gradient(to right, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)">
55
+ </MSlider>
56
+
57
+ <MSlider v-model="opacitySlider" @change="onSliderChangeComplete"
58
+ :background="`linear-gradient(to right, rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, 0), rgba(${finalColor.r}, ${finalColor.g}, ${finalColor.b}, 1))`">
59
+ </MSlider>
60
+ </div>
61
+ </div>
62
+
63
+ <div :class="style.colorActions">
64
+ <template v-if="rgbOrHex">
65
+ <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="rInputElement"
66
+ v-model="rInput" name="r" placeholder="R" />
67
+ <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="gInputElement"
68
+ v-model="gInput" name="g" placeholder="G" />
69
+ <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="bInputElement"
70
+ v-model="bInput" name="b" placeholder="B" />
71
+ <MInput @keyup.enter="updateFromRGB" @blur="updateFromRGB" block ref="aInputElement"
72
+ v-model="aInput" name="a" placeholder="A" />
73
+ </template>
74
+ <template v-else>
75
+ <MInput @keyup.enter="commitHexInput" @blur="commitHexInput" v-model="hexInputUser" block
76
+ ref="hexInputElement" name="hex" placeholder="#00000000" />
77
+ </template>
78
+
79
+ <MButton icon @click.prevent="rgbOrHex = !rgbOrHex">
80
+
81
+ <svg style="width: 18px; height: 18px;" xmlns="http://www.w3.org/2000/svg"
82
+ viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
83
+ <path fill-rule="evenodd"
84
+ d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
85
+ clip-rule="evenodd" />
86
+ </svg>
87
+
88
+ </MButton>
89
+ <MButton icon @click.prevent="toggleInherit">
90
+ <svg style="width: 18px; height: 18px;" xmlns="http://www.w3.org/2000/svg" width="24"
91
+ height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
92
+ stroke-linecap="round" stroke-linejoin="round">
93
+ <path d="M21 6H3" />
94
+ <path d="M7 12H3" />
95
+ <path d="M7 18H3" />
96
+ <path d="M12 18a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L11 14" />
97
+ <path d="M11 10v4h4" />
98
+ </svg>
99
+ </MButton>
100
+ </div>
101
+ </div>
102
+ </transition>
103
+ </Teleport>
104
+ </div>
105
+ </template>
106
+
107
+ <script lang="ts">
108
+ import { onClickOutside, useEyeDropper, useMouseInElement, useMousePressed } from '@vueuse/core';
109
+ import { computed, defineComponent, onMounted, type PropType, reactive, ref, toRef, watch } from "vue";
110
+ import * as style from './style';
111
+ import { scale } from "../../styles/transitions.css";
112
+ import { hexToRGB, type HSV, HSVToRGBA, HueToRGBA, RGBAToHex, RGBToHSV } from './utils';
113
+ import { usePopper } from "../../composables/usePopper";
114
+ import type { Placement } from "@popperjs/core";
115
+ import { useZIndex } from "../../composables/useZIndex";
116
+ import MSlider from "../../components/slider/Slider.vue";
117
+ import MButton from "../../components/button/Button.vue";
118
+ import MInput from "../../components/input/Input.vue";
119
+
120
+ export default defineComponent({
121
+ name: "MColorPicker",
122
+ props: {
123
+ modelValue: {
124
+ type: String,
125
+ default: "#FFFFFF"
126
+ },
127
+ modelModifiers: {
128
+ type: Object,
129
+ default: () => ({})
130
+ },
131
+ transfer: {
132
+ type: Boolean,
133
+ default: false
134
+ },
135
+ placement: {
136
+ type: String as PropType<Placement>,
137
+ default: "bottom-start"
138
+ },
139
+ zIndex: {
140
+ type: Number
141
+ },
142
+ defaultColor: {
143
+ type: String,
144
+ default: "#FFFFFF"
145
+ }
146
+ },
147
+ emits: ['update:modelValue'],
148
+ components: {
149
+ MSlider,
150
+ MInput,
151
+ MButton
152
+ },
153
+ setup(props, { emit }) {
154
+ const modelModifiers = props.modelModifiers;
155
+ const { isSupported: canPickColor, open: openEyeDrop, sRGBHex: eyeDropColor } = useEyeDropper();
156
+ const { nextZIndex } = useZIndex();
157
+ const isOpen = ref(false);
158
+ const rgbOrHex = ref<boolean>(false);
159
+ const hexInput = ref("");
160
+ const rInput = ref("");
161
+ const gInput = ref("");
162
+ const bInput = ref("");
163
+ const aInput = ref("");
164
+ const internalValue = ref(props.modelValue);
165
+
166
+ const picker = ref<HTMLElement>();
167
+ const wrapper = ref<HTMLElement>();
168
+ const referenceRef = ref<HTMLElement>();
169
+ const transfer = toRef(props, 'transfer');
170
+ const placement = toRef(props, 'placement');
171
+ const hexInputElement = ref();
172
+ const hueSlider = ref(0);
173
+ const opacitySlider = ref(100);
174
+ const dragging = ref(false);
175
+ const position = reactive({
176
+ x: 0,
177
+ y: 0
178
+ });
179
+ const zIndex = ref(props.zIndex ?? nextZIndex());
180
+
181
+ const { elementX, elementY } = useMouseInElement(picker);
182
+ const { pressed } = useMousePressed();
183
+ const { popper, transferTo, updatePopper } = usePopper({
184
+ reference: referenceRef,
185
+ placement,
186
+ transfer,
187
+ wrapper,
188
+ isDrop: false,
189
+ offset: [-2, 2]
190
+ });
191
+ const brightness = computed(() => {
192
+ return 1 - position.y;
193
+ })
194
+
195
+ const saturation = computed(() => {
196
+ return position.x;
197
+ })
198
+
199
+ const finalColor = computed(() => {
200
+ if (hexInput.value === "inherit") {
201
+ return { r: 0, g: 0, b: 0, a: 0 };
202
+ }
203
+ return HSVToRGBA(hueSlider.value * 3.6, brightness.value, saturation.value, opacitySlider.value / 100);
204
+ });
205
+
206
+ const mainColor = computed(() => {
207
+ return HueToRGBA(hueSlider.value * 3.6, opacitySlider.value / 100);
208
+ });
209
+
210
+ const emitModelValue = () => {
211
+ emit('update:modelValue', hexInput.value);
212
+ };
213
+
214
+ const setInputs = () => {
215
+ if (hexInput.value !== 'inherit') {
216
+ hexInput.value = RGBAToHex(finalColor.value.r, finalColor.value.g, finalColor.value.b, finalColor.value.a);
217
+ }
218
+ rInput.value = finalColor.value.r.toString();
219
+ gInput.value = finalColor.value.g.toString();
220
+ bInput.value = finalColor.value.b.toString();
221
+ aInput.value = finalColor.value.a.toFixed(2).toString();
222
+
223
+ if (!modelModifiers.lazy) {
224
+ emitModelValue();
225
+ }
226
+ };
227
+
228
+ watch(() => finalColor.value, () => {
229
+ setInputs();
230
+ });
231
+
232
+ const updateFromHSV = (hsv: HSV, alpha: number) => {
233
+ position.x = hsv.s;
234
+ position.y = 1 - hsv.v;
235
+ hueSlider.value = ((1 / 360) * hsv.h) * 100;
236
+ opacitySlider.value = alpha * 100;
237
+ setInputs();
238
+ }
239
+
240
+ const updateFromHex = () => {
241
+ if (hexInput.value === 'inherit') {
242
+ if (modelModifiers.lazy) {
243
+ emitModelValue();
244
+ }
245
+ return;
246
+ }
247
+
248
+ let hex = hexInput.value.replace("#", "");
249
+ if (hex.length === 3) {
250
+ hex = hex.split("").map(char => char + char).join("");
251
+ }
252
+ const color = hexToRGB(`#${hex}`);
253
+ if (color.a === 0) {
254
+ color.a = 1;
255
+ opacitySlider.value = 100;
256
+ }
257
+
258
+ updateFromHSV(RGBToHSV(color.r, color.g, color.b), color.a);
259
+ if (modelModifiers.lazy) {
260
+ emitModelValue();
261
+ }
262
+ };
263
+
264
+
265
+ const toggleInherit = () => {
266
+ if (props.modelValue === "inherit") {
267
+ hexInput.value = "#FFFFFF";
268
+ } else {
269
+ hexInput.value = "inherit";
270
+ }
271
+ emitModelValue();
272
+ };
273
+
274
+ const updateFromRGB = () => {
275
+ let red = parseInt(rInput.value);
276
+ if (isNaN(red) || red > 255 || red < 0) {
277
+ red = finalColor.value.r;
278
+ }
279
+
280
+ let green = parseInt(gInput.value);
281
+ if (isNaN(green) || green > 255 || green < 0) {
282
+ green = finalColor.value.g;
283
+ }
284
+
285
+ let blue = parseInt(bInput.value);
286
+ if (isNaN(blue) || blue > 255 || blue < 0) {
287
+ blue = finalColor.value.b;
288
+ }
289
+
290
+ let alpha = parseFloat(aInput.value);
291
+ if (isNaN(alpha) || alpha > 1 || alpha < 0) {
292
+ alpha = finalColor.value.a;
293
+ }
294
+
295
+ updateFromHSV(RGBToHSV(red, green, blue), alpha);
296
+
297
+ if (hexInput.value === 'inherit') {
298
+ hexInput.value = RGBAToHex(red, green, blue, alpha);
299
+ emitModelValue();
300
+ } else if (modelModifiers.lazy) {
301
+ emitModelValue();
302
+ }
303
+ };
304
+
305
+ watch(() => pressed.value, () => {
306
+ if (!pressed.value) {
307
+ dragging.value = false;
308
+ }
309
+ });
310
+
311
+ watch(() => dragging.value, (newVal, oldVal) => {
312
+ if (oldVal && !newVal && modelModifiers.lazy) {
313
+ emitModelValue();
314
+ }
315
+ });
316
+
317
+ watch(() => [elementX.value, elementY.value], () => {
318
+ if (!dragging.value) {
319
+ return;
320
+ }
321
+
322
+ setThumbPosition();
323
+ });
324
+
325
+ watch(() => props.modelValue, () => {
326
+ let val = props.modelValue;
327
+ if (!val || val.trim() === "") {
328
+ val = props.defaultColor ?? "#FFFFFF";
329
+ }
330
+ hexInput.value = val;
331
+ updateFromHex();
332
+ });
333
+
334
+ const setThumbPosition = () => {
335
+ position.x = ((Math.min(Math.max(elementX.value, 0), picker.value?.clientWidth || 0) / (picker.value?.clientWidth || 0)));
336
+ position.y = ((Math.min(Math.max(elementY.value, 0), picker.value?.clientHeight || 0) / (picker.value?.clientHeight || 0)));
337
+ };
338
+
339
+ const handleMouseDown = () => {
340
+ if (hexInput.value === 'inherit') {
341
+ hexInput.value = RGBAToHex(finalColor.value.r, finalColor.value.g, finalColor.value.b, finalColor.value.a);
342
+ emitModelValue();
343
+ }
344
+ dragging.value = true;
345
+ setThumbPosition();
346
+ };
347
+
348
+ onMounted(() => {
349
+ const match = props.modelValue.match(/rgba?\s*\(\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*(?:[,\s]\s*([\d.]+%?))?\s*\)/i);
350
+
351
+ if (match) {
352
+ const [_, r, g, b, a] = match;
353
+ hexInput.value = RGBAToHex(parseInt(r), parseInt(g), parseInt(b), a
354
+ ? a.endsWith('%')
355
+ ? parseFloat(a) / 100
356
+ : parseFloat(a)
357
+ : 1.0);
358
+ } else {
359
+ hexInput.value = props.modelValue;
360
+ }
361
+
362
+ updateFromHex();
363
+ });
364
+
365
+ onClickOutside(popper, (e) => {
366
+ isOpen.value = false;
367
+ });
368
+
369
+
370
+ watch(() => eyeDropColor.value, () => {
371
+ if (eyeDropColor.value) {
372
+ hexInput.value = eyeDropColor.value;
373
+ emitModelValue();
374
+ updateFromHex();
375
+ }
376
+ });
377
+
378
+ const openEyeDropper = () => {
379
+ try {
380
+ openEyeDrop();
381
+ } catch (e) {
382
+ console.log(e);
383
+ }
384
+ }
385
+
386
+ const onSliderChangeComplete = () => {
387
+ if (modelModifiers.lazy) {
388
+ emitModelValue();
389
+ }
390
+ }
391
+
392
+ const hexInputUser = ref(hexInput.value);
393
+
394
+ watch(hexInput, (newVal) => {
395
+ if (document.activeElement !== hexInputElement.value?.$el?.querySelector('input')) {
396
+ hexInputUser.value = newVal;
397
+ }
398
+ });
399
+
400
+ const commitHexInput = () => {
401
+ if (hexInputUser.value.trim() === '') {
402
+ hexInputUser.value = hexInput.value;
403
+ return;
404
+ }
405
+
406
+ hexInput.value = hexInputUser.value;
407
+ updateFromHex();
408
+ };
409
+
410
+
411
+ return {
412
+ isOpen,
413
+ popper,
414
+ transferTo,
415
+ wrapper,
416
+ referenceRef,
417
+ updateFromRGB,
418
+ updateFromHex,
419
+ setInputs,
420
+ rgbOrHex,
421
+ hexInputElement,
422
+ hexInput,
423
+ rInput,
424
+ gInput,
425
+ bInput,
426
+ aInput,
427
+ finalColor,
428
+ hueSlider,
429
+ opacitySlider,
430
+ dragging,
431
+ setThumbPosition,
432
+ position,
433
+ elementX,
434
+ pressed,
435
+ handleMouseDown,
436
+ picker,
437
+ style,
438
+ mainColor,
439
+ zIndex,
440
+ canPickColor,
441
+ openEyeDropper,
442
+ scale,
443
+ onSliderChangeComplete,
444
+ hexInputUser,
445
+ toggleInherit,
446
+ commitHexInput
447
+ };
448
+ }
449
+ })
450
+ </script>