@witchcraft/ui 0.0.1 → 0.1.0
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.
- package/README.md +18 -28
- package/dist/module.d.mts +3 -1
- package/dist/module.d.ts +3 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +20 -11
- package/dist/runtime/assets/base.css +1 -1
- package/dist/runtime/assets/locales/en.json +2 -2
- package/dist/runtime/assets/tailwind.css +1 -1
- package/dist/runtime/assets/utils.css +1 -0
- package/dist/runtime/build/WitchcraftUiResolver.js +1 -1
- package/dist/runtime/components/Icon/Icon.vue +10 -5
- package/dist/runtime/components/LibButton/LibButton.vue +41 -46
- package/dist/runtime/components/LibCheckbox/LibCheckbox.vue +7 -3
- package/dist/runtime/components/LibColorInput/LibColorInput.vue +111 -36
- package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.d.ts +2 -0
- package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.js +26 -9
- package/dist/runtime/components/LibColorPicker/LibColorPicker.vue +242 -131
- package/dist/runtime/components/LibColorPicker/utils/safeConvertToHsva.d.ts +2 -0
- package/dist/runtime/components/LibColorPicker/utils/safeConvertToHsva.js +18 -0
- package/dist/runtime/components/LibColorPicker/utils/safeConvertToRgba.d.ts +2 -0
- package/dist/runtime/components/LibColorPicker/utils/safeConvertToRgba.js +17 -0
- package/dist/runtime/components/LibColorPicker/utils/toLowPrecisionRgbaString.d.ts +2 -0
- package/dist/runtime/components/LibColorPicker/utils/toLowPrecisionRgbaString.js +8 -0
- package/dist/runtime/components/LibColorPicker/utils/truncate.d.ts +1 -0
- package/dist/runtime/components/LibColorPicker/utils/truncate.js +5 -0
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.js +1 -1
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +11 -8
- package/dist/runtime/components/LibDatePicker/LibDatePicker.vue +4 -17
- package/dist/runtime/components/LibDatePicker/LibRangeDatePicker.vue +192 -131
- package/dist/runtime/components/LibDatePicker/LibSingleDatePicker.vue +183 -115
- package/dist/runtime/components/LibDatePicker/LibTimeZonePicker.vue +3 -3
- package/dist/runtime/components/LibDebug/LibDebug.vue +15 -5
- package/dist/runtime/components/LibDevOnly/LibDevOnly.vue +1 -3
- package/dist/runtime/components/LibFileInput/LibFileInput.vue +54 -28
- package/dist/runtime/components/{LibInput/LibInput.stories.d.ts → LibInputDeprecated/LibInputDeprecated.stories.d.ts} +6 -6
- package/dist/runtime/components/{LibInput/LibInput.stories.js → LibInputDeprecated/LibInputDeprecated.stories.js} +64 -19
- package/{src/runtime/components/LibInput/LibInput.vue → dist/runtime/components/LibInputDeprecated/LibInputDeprecated.vue} +40 -33
- package/dist/runtime/components/LibLabel/LibLabel.vue +2 -2
- package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.d.ts +1 -1
- package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.js +5 -4
- package/dist/runtime/components/LibMultiValues/LibMultiValues.vue +11 -12
- package/dist/runtime/components/LibNotifications/LibNotification.vue +19 -10
- package/dist/runtime/components/LibNotifications/LibNotifications.stories.js +2 -2
- package/dist/runtime/components/LibNotifications/LibNotifications.vue +20 -11
- package/dist/runtime/components/LibPagination/LibPagination.stories.js +2 -2
- package/dist/runtime/components/LibPagination/LibPagination.vue +19 -19
- package/dist/runtime/components/LibPalette/LibPalette.vue +3 -3
- package/dist/runtime/components/LibPopup/LibPopup.stories.js +2 -2
- package/dist/runtime/components/LibPopup/LibPopup.vue +30 -66
- package/dist/runtime/components/LibProgressBar/LibProgressBar.vue +3 -1
- package/dist/runtime/components/LibRecorder/LibRecorder.vue +2 -2
- package/dist/runtime/components/LibRoot/LibRoot.vue +14 -1
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.stories.js +1 -1
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.vue +5 -7
- package/dist/runtime/components/LibSuggestions/LibSuggestions.vue +42 -25
- package/dist/runtime/components/LibTable/LibTable.vue +8 -8
- package/dist/runtime/components/Scrolling.stories.d.ts +6 -0
- package/dist/runtime/components/Scrolling.stories.js +44 -0
- package/dist/runtime/components/Template/NAME.vue +1 -1
- package/dist/runtime/components/TestControls/TestControls.vue +1 -1
- package/dist/runtime/components/index.d.ts +12 -11
- package/dist/runtime/components/index.js +12 -11
- package/dist/runtime/components/shared/props.d.ts +81 -16
- package/dist/runtime/components/shared/storyHelpers/playInput.js +5 -5
- package/dist/runtime/components/shared/storyHelpers/playSuggestions.js +15 -11
- package/dist/runtime/composables/index.d.ts +5 -0
- package/dist/runtime/composables/index.js +5 -0
- package/dist/runtime/composables/useDivideAttrs.js +1 -0
- package/dist/runtime/composables/useDragWithThreshold.d.ts +71 -0
- package/dist/runtime/composables/useDragWithThreshold.js +40 -0
- package/dist/runtime/composables/usePreHydrationValue.d.ts +12 -0
- package/dist/runtime/composables/usePreHydrationValue.js +15 -0
- package/dist/runtime/composables/useSetupI18n.d.ts +2 -0
- package/dist/runtime/composables/useSetupI18n.js +5 -1
- package/dist/runtime/composables/useSuggestions.d.ts +7 -5
- package/dist/runtime/composables/useSuggestions.js +94 -57
- package/dist/runtime/directives/vResizableCols.js +3 -1
- package/dist/runtime/helpers/NotificationHandler.d.ts +5 -0
- package/dist/runtime/helpers/index.d.ts +3 -1
- package/dist/runtime/helpers/index.js +3 -1
- package/dist/runtime/types/index.d.ts +6 -0
- package/dist/runtime/utils/notifyIfError.d.ts +14 -0
- package/dist/runtime/utils/notifyIfError.js +29 -0
- package/package.json +18 -20
- package/src/module.ts +31 -12
- package/src/runtime/assets/base.css +10 -1
- package/src/runtime/assets/locales/en.json +2 -2
- package/src/runtime/assets/tailwind.css +1 -1
- package/src/runtime/assets/{style.css → utils.css} +86 -4
- package/src/runtime/build/WitchcraftUiResolver.ts +1 -1
- package/src/runtime/components/Icon/Icon.vue +10 -5
- package/src/runtime/components/LibButton/LibButton.vue +41 -46
- package/src/runtime/components/LibCheckbox/LibCheckbox.vue +7 -3
- package/src/runtime/components/LibColorInput/LibColorInput.vue +111 -36
- package/src/runtime/components/LibColorPicker/LibColorPicker.stories.ts +25 -4
- package/src/runtime/components/LibColorPicker/LibColorPicker.vue +242 -131
- package/src/runtime/components/LibColorPicker/utils/safeConvertToHsva.ts +25 -0
- package/src/runtime/components/LibColorPicker/utils/safeConvertToRgba.ts +23 -0
- package/src/runtime/components/LibColorPicker/utils/toLowPrecisionRgbaString.ts +13 -0
- package/src/runtime/components/LibColorPicker/utils/truncate.ts +6 -0
- package/src/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.ts +1 -1
- package/src/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +11 -8
- package/src/runtime/components/LibDatePicker/LibDatePicker.vue +4 -17
- package/src/runtime/components/LibDatePicker/LibRangeDatePicker.vue +192 -131
- package/src/runtime/components/LibDatePicker/LibSingleDatePicker.vue +183 -115
- package/src/runtime/components/LibDatePicker/LibTimeZonePicker.vue +3 -3
- package/src/runtime/components/LibDebug/LibDebug.vue +15 -5
- package/src/runtime/components/LibDevOnly/LibDevOnly.vue +1 -3
- package/src/runtime/components/LibFileInput/LibFileInput.vue +54 -28
- package/src/runtime/components/{LibInput/LibInput.stories.ts → LibInputDeprecated/LibInputDeprecated.stories.ts} +64 -19
- package/{dist/runtime/components/LibInput/LibInput.vue → src/runtime/components/LibInputDeprecated/LibInputDeprecated.vue} +40 -33
- package/src/runtime/components/LibLabel/LibLabel.vue +2 -2
- package/src/runtime/components/LibMultiValues/LibMultiValues.stories.ts +5 -4
- package/src/runtime/components/LibMultiValues/LibMultiValues.vue +11 -12
- package/src/runtime/components/LibNotifications/LibNotification.vue +19 -10
- package/src/runtime/components/LibNotifications/LibNotifications.stories.ts +2 -2
- package/src/runtime/components/LibNotifications/LibNotifications.vue +20 -11
- package/src/runtime/components/LibPagination/LibPagination.stories.ts +2 -2
- package/src/runtime/components/LibPagination/LibPagination.vue +19 -19
- package/src/runtime/components/LibPalette/LibPalette.vue +3 -3
- package/src/runtime/components/LibPopup/LibPopup.stories.ts +2 -2
- package/src/runtime/components/LibPopup/LibPopup.vue +30 -66
- package/src/runtime/components/LibProgressBar/LibProgressBar.vue +3 -1
- package/src/runtime/components/LibRecorder/LibRecorder.vue +2 -2
- package/src/runtime/components/LibRoot/LibRoot.vue +14 -1
- package/src/runtime/components/LibSimpleInput/LibSimpleInput.stories.ts +1 -1
- package/src/runtime/components/LibSimpleInput/LibSimpleInput.vue +5 -7
- package/src/runtime/components/LibSuggestions/LibSuggestions.vue +42 -25
- package/src/runtime/components/LibTable/LibTable.vue +8 -8
- package/src/runtime/components/Scrolling.stories.ts +58 -0
- package/src/runtime/components/Template/NAME.vue +1 -1
- package/src/runtime/components/TestControls/TestControls.vue +1 -1
- package/src/runtime/components/index.ts +12 -12
- package/src/runtime/components/shared/props.ts +82 -19
- package/src/runtime/components/shared/storyHelpers/playInput.ts +6 -5
- package/src/runtime/components/shared/storyHelpers/playSuggestions.ts +25 -11
- package/src/runtime/composables/index.ts +5 -0
- package/src/runtime/composables/useDarkMode.ts +2 -2
- package/src/runtime/composables/useDivideAttrs.ts +1 -0
- package/src/runtime/composables/useDragWithThreshold.ts +108 -0
- package/src/runtime/composables/usePreHydrationValue.ts +30 -0
- package/src/runtime/composables/useSetupI18n.ts +8 -2
- package/src/runtime/composables/useSuggestions.ts +92 -45
- package/src/runtime/directives/vResizableCols.ts +3 -1
- package/src/runtime/helpers/NotificationHandler.ts +5 -0
- package/src/runtime/helpers/index.ts +3 -1
- package/src/runtime/types/index.ts +5 -0
- package/src/runtime/utils/notifyIfError.ts +45 -0
- package/dist/runtime/assets/style.css +0 -1
- package/dist/runtime/helpers/addValue.d.ts +0 -1
- package/dist/runtime/helpers/addValue.js +0 -8
- package/src/runtime/helpers/addValue.ts +0 -10
- /package/dist/runtime/components/{reset.stories.d.ts → Reset.stories.d.ts} +0 -0
- /package/dist/runtime/components/{reset.stories.js → Reset.stories.js} +0 -0
- /package/src/runtime/components/{reset.stories.ts → Reset.stories.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
:id="id ?? fallbackId"
|
|
3
4
|
:aria-label="t('color-picker.aria')"
|
|
4
5
|
:class="twMerge(`color-picker
|
|
5
6
|
[--slider-size:calc(var(--spacing)_*_4)]
|
|
@@ -20,11 +21,12 @@
|
|
|
20
21
|
`,
|
|
21
22
|
border && `
|
|
22
23
|
border rounded-sm border-neutral-600
|
|
23
|
-
|
|
24
|
+
`,
|
|
25
|
+
($attrs as any)?.class
|
|
24
26
|
)"
|
|
25
27
|
>
|
|
26
28
|
<div
|
|
27
|
-
:class="`picker
|
|
29
|
+
:class="`color-picker--all-picker
|
|
28
30
|
no-touch-action
|
|
29
31
|
w-full
|
|
30
32
|
aspect-square
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
rounded-sm
|
|
34
36
|
focus:border-accent-500
|
|
35
37
|
`"
|
|
36
|
-
@pointerdown="slider.pointerdown($event, '
|
|
38
|
+
@pointerdown="slider.pointerdown($event, 'all')"
|
|
37
39
|
@pointerleave="slider.pointerleave($event)"
|
|
38
40
|
>
|
|
39
41
|
<canvas
|
|
@@ -43,8 +45,10 @@
|
|
|
43
45
|
<div
|
|
44
46
|
aria-live="assertive"
|
|
45
47
|
:aria-description="ariaDescription"
|
|
48
|
+
:aria-label="`${t('color-picker.aria.saturation')}: ${localColor.percent.s}, ${t('color-picker.aria.value')}: ${localColor.percent.s}`"
|
|
46
49
|
:class="`
|
|
47
|
-
handle
|
|
50
|
+
color-picker--all-handle
|
|
51
|
+
${handleClasses}
|
|
48
52
|
border-[var(--fg)]
|
|
49
53
|
hover:shadow-black
|
|
50
54
|
active:shadow-black
|
|
@@ -53,15 +57,13 @@
|
|
|
53
57
|
:style="`
|
|
54
58
|
left: calc(${localColor.percent.s}% - var(--slider-size)/2);
|
|
55
59
|
top: calc(${localColor.percent.v}% - var(--slider-size)/2);
|
|
56
|
-
background: ${
|
|
60
|
+
background: ${asRgbaString};
|
|
57
61
|
`"
|
|
58
|
-
@keydown="slider.keydown($event, '
|
|
59
|
-
|
|
60
|
-
<aria :value="`${t('color-picker.aria.saturation')}: ${localColor.percent.s}, ${t('color-picker.aria.value')}: ${localColor.percent.s}`"/>
|
|
61
|
-
</div>
|
|
62
|
+
@keydown="slider.keydown($event, 'all')"
|
|
63
|
+
/>
|
|
62
64
|
</div>
|
|
63
65
|
<div
|
|
64
|
-
:class="`hue-slider ${sliderClasses}`"
|
|
66
|
+
:class="`color-picker--hue-slider ${sliderClasses}`"
|
|
65
67
|
@pointerdown="slider.pointerdown($event, 'hue')"
|
|
66
68
|
>
|
|
67
69
|
<canvas
|
|
@@ -76,7 +78,10 @@
|
|
|
76
78
|
:aria-label="t('color-picker.aria.hue')"
|
|
77
79
|
:aria-description="ariaDescription"
|
|
78
80
|
tabindex="0"
|
|
79
|
-
:class="`
|
|
81
|
+
:class="`
|
|
82
|
+
color-picker--hue-handle
|
|
83
|
+
${handleClasses}
|
|
84
|
+
`"
|
|
80
85
|
:style="`left: calc(${localColor.percent.h}% - var(--slider-size)/2)`"
|
|
81
86
|
@keydown="slider.keydown($event, 'hue')"
|
|
82
87
|
/>
|
|
@@ -84,8 +89,10 @@
|
|
|
84
89
|
<div
|
|
85
90
|
v-if="allowAlpha"
|
|
86
91
|
|
|
87
|
-
:class="`
|
|
88
|
-
|
|
92
|
+
:class="`
|
|
93
|
+
color-picker--alpha-slider
|
|
94
|
+
${sliderClasses}
|
|
95
|
+
`"
|
|
89
96
|
@pointerdown="slider.pointerdown($event, 'alpha')"
|
|
90
97
|
>
|
|
91
98
|
<canvas
|
|
@@ -100,12 +107,13 @@
|
|
|
100
107
|
:aria-valuemax="100"
|
|
101
108
|
:aria-description="ariaDescription"
|
|
102
109
|
tabindex="0"
|
|
103
|
-
:class="`handle ${handleClasses}
|
|
110
|
+
:class="`color-picker--alpha-handle ${handleClasses}`"
|
|
104
111
|
:style="`left: calc(${localColor.percent.a}% - var(--slider-size)/2)`"
|
|
112
|
+
@keydown="slider.keydown($event, 'alpha')"
|
|
105
113
|
/>
|
|
106
114
|
</div>
|
|
107
|
-
<div class="color-
|
|
108
|
-
<div class=" color-wrapper
|
|
115
|
+
<div class="color-picker--footer flex w-full flex-1 gap-2">
|
|
116
|
+
<div class=" color-picker--preview-wrapper
|
|
109
117
|
bg-transparency-squares
|
|
110
118
|
relative
|
|
111
119
|
aspect-square
|
|
@@ -114,36 +122,47 @@
|
|
|
114
122
|
shadow-xs
|
|
115
123
|
"
|
|
116
124
|
>
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
<div class="
|
|
126
|
+
color-picker--footer--preview
|
|
119
127
|
size-full
|
|
120
128
|
rounded-full
|
|
121
129
|
border-2
|
|
122
130
|
border-neutral-600
|
|
123
131
|
dark:border-neutral-300
|
|
124
132
|
"
|
|
125
|
-
:style="`background
|
|
133
|
+
:style="`background: ${asRgbaString}`"
|
|
126
134
|
/>
|
|
127
135
|
</div>
|
|
128
|
-
<div class="color-
|
|
136
|
+
<div class="color-picker--input-group flex flex-1 items-center gap-2">
|
|
129
137
|
<slot name="input">
|
|
130
138
|
<lib-simple-input
|
|
131
|
-
|
|
139
|
+
:valid="valid"
|
|
140
|
+
class="color-picker--input w-full"
|
|
132
141
|
:aria-label="label"
|
|
133
|
-
|
|
142
|
+
v-model="localInputString"
|
|
134
143
|
@input="parseInput"
|
|
144
|
+
@blur="onBlurFixInvalidValue"
|
|
135
145
|
/>
|
|
136
|
-
<lib-button :aria-label="t('copy')" @click="copy()">
|
|
146
|
+
<lib-button class="color-picker--copy-button" :aria-label="t('copy')" @click="copy(copyTransform?.(localColor.val, localColorString) ?? localColorString)">
|
|
137
147
|
<icon><i-fa6-regular-copy/></icon>
|
|
138
148
|
</lib-button>
|
|
139
149
|
</slot>
|
|
140
150
|
</div>
|
|
141
|
-
<!-- <lib-button @click="emits('update:modelValue', localColor.val)">Save</lib-button> -->
|
|
142
151
|
</div>
|
|
143
152
|
<slot name="buttons">
|
|
144
|
-
<div class="save-cancel-group flex w-full items-center justify-center gap-2">
|
|
145
|
-
<lib-button
|
|
146
|
-
|
|
153
|
+
<div class="color-picker--save-cancel-group flex w-full items-center justify-center gap-2">
|
|
154
|
+
<lib-button
|
|
155
|
+
class="color-picker--save-button"
|
|
156
|
+
@click="save()"
|
|
157
|
+
>
|
|
158
|
+
{{ t("save") }}
|
|
159
|
+
</lib-button>
|
|
160
|
+
<lib-button
|
|
161
|
+
class="color-picker--cancel-button"
|
|
162
|
+
@click="emits('cancel')"
|
|
163
|
+
>
|
|
164
|
+
{{ t("cancel") }}
|
|
165
|
+
</lib-button>
|
|
147
166
|
</div>
|
|
148
167
|
</slot>
|
|
149
168
|
</div>
|
|
@@ -152,11 +171,18 @@
|
|
|
152
171
|
<script setup lang="ts">
|
|
153
172
|
/* todo change to colorjsio for less dependencies */
|
|
154
173
|
import { castType } from "@alanscodelog/utils/castType.js"
|
|
174
|
+
import { clampNumber } from "@alanscodelog/utils/clampNumber.js"
|
|
155
175
|
import { isArray } from "@alanscodelog/utils/isArray.js"
|
|
156
|
-
import {
|
|
157
|
-
import
|
|
176
|
+
import { unreachable } from "@alanscodelog/utils/unreachable.js"
|
|
177
|
+
import Color from "colorjs.io"
|
|
178
|
+
import { computed, onMounted, reactive, type Ref, ref, type UnwrapRef,useAttrs, watch } from "vue"
|
|
179
|
+
|
|
180
|
+
import { safeConvertToHsva } from "./utils/safeConvertToHsva.js"
|
|
181
|
+
import { safeConvertToRgba } from "./utils/safeConvertToRgba.js"
|
|
182
|
+
import { toLowPrecisionRgbaString } from "./utils/toLowPrecisionRgbaString.js"
|
|
158
183
|
|
|
159
184
|
import { useInjectedI18n } from "../../composables/useInjectedI18n.js"
|
|
185
|
+
import { copy } from "../../helpers/copy.js"
|
|
160
186
|
import type { HsvaColor, RgbaColor } from "../../types/index.js"
|
|
161
187
|
import { twMerge } from "../../utils/twMerge.js"
|
|
162
188
|
import aria from "../Aria/Aria.vue"
|
|
@@ -169,6 +195,8 @@ defineOptions({
|
|
|
169
195
|
name: "lib-color-picker",
|
|
170
196
|
})
|
|
171
197
|
|
|
198
|
+
const $attrs = useAttrs()
|
|
199
|
+
|
|
172
200
|
const t = useInjectedI18n()
|
|
173
201
|
|
|
174
202
|
const sliderClasses = `
|
|
@@ -181,6 +209,7 @@ const sliderClasses = `
|
|
|
181
209
|
`
|
|
182
210
|
|
|
183
211
|
const handleClasses = `
|
|
212
|
+
handle
|
|
184
213
|
h-[var(--slider-size)]
|
|
185
214
|
w-[var(--slider-size)]
|
|
186
215
|
shadow-xs
|
|
@@ -194,37 +223,61 @@ const handleClasses = `
|
|
|
194
223
|
active:border-accent-500
|
|
195
224
|
hover:border-accent-500
|
|
196
225
|
`
|
|
226
|
+
const emits = defineEmits<{
|
|
227
|
+
(e: "save", val: RgbaColor): void
|
|
228
|
+
(e: "cancel"): void
|
|
229
|
+
}>()
|
|
197
230
|
|
|
198
|
-
|
|
199
|
-
const ariaDescription = t("color-picker.aria.description")
|
|
200
|
-
|
|
201
|
-
const $value = defineModel<RgbaColor>({ required: false, default: () => ({ r: 0, g: 0, b: 0 }) })
|
|
202
|
-
|
|
203
|
-
const fallbackId = getFallbackId()
|
|
204
231
|
const props = withDefaults(defineProps<
|
|
205
|
-
|
|
232
|
+
LabelProps
|
|
206
233
|
& LinkableByIdProps
|
|
207
234
|
& {
|
|
208
235
|
allowAlpha?: boolean
|
|
236
|
+
/**
|
|
237
|
+
* The precision of the rgba string representation of the color. Defaults to 3. Extra trailing zeros are removed for a prettier number.
|
|
238
|
+
*
|
|
239
|
+
* Does not affect the number saved unless the user manually edits the color.
|
|
240
|
+
*
|
|
241
|
+
* Ignored if `customRepresentation` is set.
|
|
242
|
+
*/
|
|
243
|
+
stringPrecision?: number
|
|
244
|
+
/** Allows overriding the string representation of the color. Useful for using a different representation than rgba (e.g. hex). The fromStringToHsva part is rarely needed as the colorjs.io library can normally parse the color. Returning undefined signals an error. */
|
|
245
|
+
customRepresentation?: {
|
|
246
|
+
fromHsvaToString: (hsva: HsvaColor, includeAlpha: boolean) => string
|
|
247
|
+
fromStringToHsva?: (string: string) => HsvaColor | undefined
|
|
248
|
+
}
|
|
209
249
|
border?: boolean
|
|
250
|
+
/** Modify what the user copies to the clipboard. */
|
|
210
251
|
copyTransform?: (val: HsvaColor, stringVal: string) => any
|
|
252
|
+
valid?: boolean
|
|
211
253
|
}>(), {
|
|
212
254
|
allowAlpha: true,
|
|
213
255
|
border: true,
|
|
256
|
+
stringPrecision: 3,
|
|
214
257
|
copyTransform: (_val: HsvaColor, stringVal: string) => stringVal,
|
|
258
|
+
customRepresentation: undefined,
|
|
259
|
+
valid: true,
|
|
215
260
|
})
|
|
216
261
|
|
|
217
262
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
263
|
+
const ariaDescription = t("color-picker.aria.description")
|
|
264
|
+
const fallbackId = getFallbackId()
|
|
265
|
+
|
|
266
|
+
const $value = defineModel<RgbaColor>({ required: false, default: () => ({ r: 0, g: 0, b: 0 }) })
|
|
267
|
+
const $tempValue = defineModel<RgbaColor | undefined>("tempValue", { required: false, default: () => (undefined) })
|
|
222
268
|
|
|
223
269
|
const pickerEl = ref<HTMLCanvasElement | null>(null)
|
|
224
270
|
const hueSliderEl = ref<HTMLCanvasElement | null>(null)
|
|
225
271
|
const alphaSliderEl = ref<HTMLCanvasElement | null>(null)
|
|
226
272
|
|
|
227
|
-
type Config = Record<string, {
|
|
273
|
+
type Config = Record<string, {
|
|
274
|
+
el: Ref<HTMLCanvasElement>
|
|
275
|
+
xKey?: keyof HsvaColor
|
|
276
|
+
yKey?: keyof HsvaColor
|
|
277
|
+
xSteps?: number
|
|
278
|
+
ySteps?: number
|
|
279
|
+
}>
|
|
280
|
+
|
|
228
281
|
const config: Config = {
|
|
229
282
|
hue: {
|
|
230
283
|
el: hueSliderEl as Ref<HTMLCanvasElement>,
|
|
@@ -236,7 +289,7 @@ const config: Config = {
|
|
|
236
289
|
xSteps: 1,
|
|
237
290
|
xKey: "a",
|
|
238
291
|
},
|
|
239
|
-
|
|
292
|
+
all: {
|
|
240
293
|
el: pickerEl as Ref<HTMLCanvasElement>,
|
|
241
294
|
xSteps: 100,
|
|
242
295
|
ySteps: 100,
|
|
@@ -244,6 +297,7 @@ const config: Config = {
|
|
|
244
297
|
yKey: "v",
|
|
245
298
|
},
|
|
246
299
|
}
|
|
300
|
+
|
|
247
301
|
const localColor = reactive<Record<"percent" | "val", HsvaColor>>({
|
|
248
302
|
percent: {
|
|
249
303
|
h: 0, s: 0, v: 0, a: 0,
|
|
@@ -253,23 +307,33 @@ const localColor = reactive<Record<"percent" | "val", HsvaColor>>({
|
|
|
253
307
|
},
|
|
254
308
|
})
|
|
255
309
|
|
|
256
|
-
const localColorString = computed(() =>
|
|
257
|
-
colord(localColor.val).toRgbString(),
|
|
258
|
-
)
|
|
259
|
-
const localColorStringOpaque = computed(() =>
|
|
260
|
-
colord({ ...localColor.val, a: 1 }).toRgbString(),
|
|
261
|
-
)
|
|
262
310
|
|
|
311
|
+
const asRgba = computed(() => {
|
|
312
|
+
const rgba = safeConvertToRgba(localColor.val, props.allowAlpha)
|
|
313
|
+
if (!rgba) unreachable()
|
|
314
|
+
return rgba
|
|
315
|
+
})
|
|
316
|
+
const asRgbaString = computed(() => {
|
|
317
|
+
const rgba = asRgba.value
|
|
318
|
+
if (!rgba) unreachable()
|
|
319
|
+
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
|
|
320
|
+
})
|
|
321
|
+
const localColorString = computed(() => {
|
|
322
|
+
if (props.customRepresentation) {
|
|
323
|
+
return props.customRepresentation.fromHsvaToString({ ...localColor.val }, props.allowAlpha)
|
|
324
|
+
}
|
|
325
|
+
return toLowPrecisionRgbaString(asRgba.value, props.allowAlpha, props.stringPrecision)
|
|
326
|
+
})
|
|
263
327
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
328
|
+
const localInputString = ref(localColorString.value)
|
|
329
|
+
// fixes the localInputString not updating when the user inputs an invalid value
|
|
330
|
+
function onBlurFixInvalidValue() {
|
|
331
|
+
if (localInputString.value !== localColorString.value) {
|
|
332
|
+
localInputString.value = localColorString.value
|
|
269
333
|
}
|
|
270
334
|
}
|
|
271
335
|
|
|
272
|
-
|
|
336
|
+
function updatePicker(el: HTMLCanvasElement, hue: number): void {
|
|
273
337
|
const ctx = el.getContext("2d")!
|
|
274
338
|
const { height, width } = el
|
|
275
339
|
ctx.clearRect(0, 0, width, height)
|
|
@@ -290,9 +354,9 @@ const updatePicker = (el: HTMLCanvasElement, hue: number): void => {
|
|
|
290
354
|
ctx.globalCompositeOperation = "source-over"
|
|
291
355
|
}
|
|
292
356
|
|
|
293
|
-
|
|
357
|
+
function updateSlider(el: HTMLCanvasElement, stops: ((i: number) => string) | string[], length: number = 360): void {
|
|
294
358
|
const ctx = el.getContext("2d")!
|
|
295
|
-
const { height, width } = el
|
|
359
|
+
const { height, width } = el
|
|
296
360
|
ctx.clearRect(0, 0, width, height)
|
|
297
361
|
|
|
298
362
|
const end = isArray(stops) ? stops.length - 1 : length
|
|
@@ -301,6 +365,7 @@ const updateSlider = (el: HTMLCanvasElement, stops: ((i: number) => string) | st
|
|
|
301
365
|
|
|
302
366
|
for (let i = 0; i < end + 1; i++) {
|
|
303
367
|
const stop = stops instanceof Function ? stops(i) : stops[i]
|
|
368
|
+
if (stop === undefined) unreachable()
|
|
304
369
|
gradient.addColorStop(i / end, stop)
|
|
305
370
|
}
|
|
306
371
|
|
|
@@ -309,8 +374,7 @@ const updateSlider = (el: HTMLCanvasElement, stops: ((i: number) => string) | st
|
|
|
309
374
|
}
|
|
310
375
|
|
|
311
376
|
|
|
312
|
-
|
|
313
|
-
// const stepWidth = width / (steps * accuracy)
|
|
377
|
+
function getVal(x: number, width: number, steps: number = 100, accuracy: number = 100, reverse = false): { val: number, percent: number } {
|
|
314
378
|
const percent = (x / width)
|
|
315
379
|
const unrounded = percent * steps
|
|
316
380
|
|
|
@@ -320,53 +384,70 @@ const getVal = (x: number, width: number, steps: number = 100, accuracy: number
|
|
|
320
384
|
if (reverse) res.val = steps - val
|
|
321
385
|
return res
|
|
322
386
|
}
|
|
387
|
+
|
|
323
388
|
type Types = keyof UnwrapRef<Config>
|
|
324
389
|
const elDragging = ref<Types | "">("")
|
|
325
390
|
let dragging = false
|
|
326
391
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
392
|
+
function moveHandle(e: { clientX: number, clientY: number }, type: string) {
|
|
393
|
+
requestAnimationFrame(() => {
|
|
394
|
+
if (type === "") return
|
|
395
|
+
const el = config[type]?.el.value
|
|
396
|
+
if (!el || !config[type]) return
|
|
397
|
+
const { x, y, width, height } = el.getBoundingClientRect()
|
|
398
|
+
|
|
399
|
+
const info = config[type]
|
|
400
|
+
if (info.xKey !== undefined) {
|
|
401
|
+
let newPosX = e.clientX - x
|
|
402
|
+
newPosX = newPosX < 0 ? 0 : newPosX > width ? width : newPosX
|
|
403
|
+
const newX = getVal(newPosX, width, info.xSteps ?? 100)
|
|
404
|
+
|
|
405
|
+
localColor.percent[info.xKey] = newX.percent
|
|
406
|
+
localColor.val[info.xKey] = newX.val
|
|
407
|
+
}
|
|
340
408
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
409
|
+
if (info.yKey !== undefined) {
|
|
410
|
+
let newPosY = e.clientY - y
|
|
411
|
+
newPosY = newPosY < 0 ? 0 : newPosY > height ? height : newPosY
|
|
412
|
+
const newY = getVal(newPosY, height, info.ySteps ?? 100, 100, true)
|
|
345
413
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
414
|
+
localColor.percent[info.yKey] = newY.percent
|
|
415
|
+
localColor.val[info.yKey] = newY.val
|
|
416
|
+
}
|
|
417
|
+
})
|
|
349
418
|
}
|
|
350
419
|
const slider = {
|
|
351
420
|
keydown: (e: KeyboardEvent, type: Types) => {
|
|
352
421
|
castType<HTMLElement | undefined>(e.target)
|
|
353
|
-
if (e.target?.getBoundingClientRect
|
|
354
|
-
e.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
422
|
+
if (e.target?.getBoundingClientRect) {
|
|
423
|
+
if (["ArrowRight", "ArrowLeft", "ArrowUp", "ArrowDown"].includes(e.key)) {
|
|
424
|
+
e.preventDefault()
|
|
425
|
+
const { x, y, width, height } = e.target.getBoundingClientRect()
|
|
426
|
+
let xDiff = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0
|
|
427
|
+
let yDiff = e.key === "ArrowUp" ? -1 : e.key === "ArrowDown" ? 1 : 0
|
|
428
|
+
if (e.shiftKey) {xDiff *= 10}
|
|
429
|
+
if (e.shiftKey) {yDiff *= 10}
|
|
430
|
+
moveHandle({ clientX: x + (width / 2) + xDiff, clientY: y + (height / 2) + yDiff }, type)
|
|
431
|
+
}
|
|
432
|
+
if (e.key === "Enter") {
|
|
433
|
+
e.preventDefault()
|
|
434
|
+
save()
|
|
435
|
+
}
|
|
361
436
|
}
|
|
362
437
|
},
|
|
363
438
|
pointerdown: (e: PointerEvent, type: Types) => {
|
|
439
|
+
const focusTargetClass = `#${props.id ?? fallbackId} .color-picker--${type}-handle`
|
|
440
|
+
const focusTarget = document.querySelector(focusTargetClass)
|
|
441
|
+
// allows enter to work when the user drags any slider as the even will be captured by the keydown listener
|
|
442
|
+
if (focusTarget instanceof HTMLElement) focusTarget.focus()
|
|
443
|
+
|
|
364
444
|
if (dragging) return
|
|
365
445
|
e.preventDefault()
|
|
366
446
|
elDragging.value = type
|
|
367
447
|
dragging = true
|
|
368
448
|
document.addEventListener("pointermove", slider.pointermove)
|
|
369
449
|
document.addEventListener("pointerup", slider.pointerup)
|
|
450
|
+
moveHandle(e, elDragging.value)
|
|
370
451
|
},
|
|
371
452
|
pointerleave: (e: PointerEvent) => {
|
|
372
453
|
if (dragging) {
|
|
@@ -374,75 +455,105 @@ const slider = {
|
|
|
374
455
|
}
|
|
375
456
|
},
|
|
376
457
|
pointermove: (e: PointerEvent) => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
requestAnimationFrame(() => {
|
|
380
|
-
const type = elDragging.value
|
|
381
|
-
moveHandle(e, type)
|
|
382
|
-
})
|
|
383
|
-
}
|
|
458
|
+
e.preventDefault()
|
|
459
|
+
moveHandle(e, elDragging.value)
|
|
384
460
|
},
|
|
385
461
|
pointerup: (e: PointerEvent) => {
|
|
386
462
|
e.preventDefault()
|
|
387
|
-
elDragging.value = ""
|
|
388
463
|
dragging = false
|
|
464
|
+
elDragging.value = ""
|
|
389
465
|
document.removeEventListener("pointermove", slider.pointermove)
|
|
390
466
|
document.removeEventListener("pointerup", slider.pointerup)
|
|
391
467
|
},
|
|
392
468
|
}
|
|
393
|
-
|
|
469
|
+
function updateSliders(_: HsvaColor): void {
|
|
394
470
|
if (alphaSliderEl.value) {
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
const
|
|
398
|
-
|
|
471
|
+
// https://colorjs.io/docs/output#get-a-displayable-css-color-value
|
|
472
|
+
const color = new Color("hsv", [_.h, _.s, _.v], _.a).to("hsl")
|
|
473
|
+
const hsl0 = color.clone()
|
|
474
|
+
hsl0.alpha = 0
|
|
475
|
+
const hsl1 = color.clone()
|
|
476
|
+
hsl1.alpha = 1
|
|
477
|
+
updateSlider(alphaSliderEl.value, [hsl0.toString(), hsl1.toString()])
|
|
399
478
|
}
|
|
400
479
|
updateSlider(hueSliderEl.value!, i => `hsl(${i} 100% 50%)`)
|
|
401
480
|
updatePicker(pickerEl.value!, _.h)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function updateValueAndPosition(_: HsvaColor): void {
|
|
484
|
+
localColor.percent.h = Math.round((_.h / 360) * 10000) / 100
|
|
485
|
+
localColor.percent.s = _.s
|
|
486
|
+
localColor.percent.v = 100 - _.v
|
|
487
|
+
localColor.percent.a =
|
|
488
|
+
props.allowAlpha
|
|
489
|
+
? _.a !== undefined
|
|
490
|
+
? _.a * 100
|
|
411
491
|
: 1
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
localColor.val = { ..._, a: props.allowAlpha ? _.a : 1 }
|
|
415
|
-
}
|
|
492
|
+
: 1
|
|
493
|
+
localColor.val = { ..._, a: props.allowAlpha ? _.a : 1 }
|
|
416
494
|
}
|
|
417
495
|
|
|
418
|
-
|
|
496
|
+
function convertAndUpdateAll(rgba: RgbaColor) {
|
|
497
|
+
const hsva = safeConvertToHsva(rgba, props.allowAlpha)
|
|
498
|
+
if (!hsva) return
|
|
499
|
+
updateSliders(hsva)
|
|
500
|
+
updateValueAndPosition(hsva)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function save(): void {
|
|
504
|
+
const rgba = safeConvertToRgba(localColor.val, props.allowAlpha)
|
|
505
|
+
if (!rgba) return
|
|
506
|
+
// update(localColor.val, { updatePosition: false, updateValue: false })
|
|
507
|
+
$value.value = rgba
|
|
508
|
+
$tempValue.value = undefined
|
|
509
|
+
emits("save", rgba)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
function parseInput(e: Event): void {
|
|
419
514
|
const val = (e.target as HTMLInputElement)?.value
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
515
|
+
const converted = props.customRepresentation?.fromStringToHsva
|
|
516
|
+
? props.customRepresentation.fromStringToHsva(val)
|
|
517
|
+
: safeConvertToHsva(val, props.allowAlpha)
|
|
518
|
+
if (converted) {
|
|
519
|
+
updateSliders(converted)
|
|
520
|
+
updateValueAndPosition(converted)
|
|
424
521
|
}
|
|
425
522
|
}
|
|
426
523
|
|
|
524
|
+
|
|
525
|
+
let disableUpdateTempValue = false
|
|
526
|
+
|
|
427
527
|
onMounted(() => {
|
|
428
|
-
|
|
429
|
-
|
|
528
|
+
convertAndUpdateAll($value.value)
|
|
529
|
+
if ($tempValue.value !== undefined) {
|
|
530
|
+
convertAndUpdateAll($tempValue.value)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const handle = document.querySelector(`#${props.id ?? fallbackId} .color-picker--all-handle`)
|
|
534
|
+
if (handle instanceof HTMLElement) handle.focus()
|
|
430
535
|
})
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
536
|
+
|
|
537
|
+
watch($value, () => {
|
|
538
|
+
convertAndUpdateAll($value.value)
|
|
434
539
|
})
|
|
435
540
|
|
|
436
|
-
watch(
|
|
437
|
-
|
|
541
|
+
watch($tempValue, () => {
|
|
542
|
+
if ($tempValue.value !== undefined) {
|
|
543
|
+
disableUpdateTempValue = true
|
|
544
|
+
convertAndUpdateAll($tempValue.value)
|
|
545
|
+
setTimeout(() => { disableUpdateTempValue = false }, 0)
|
|
546
|
+
}
|
|
438
547
|
})
|
|
439
548
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
549
|
+
watch(localColor, () => {
|
|
550
|
+
updateSliders(localColor.val)
|
|
551
|
+
localInputString.value = localColorString.value
|
|
552
|
+
if (disableUpdateTempValue) return
|
|
553
|
+
const rgba = safeConvertToRgba(localColor.val, props.allowAlpha)
|
|
554
|
+
if (!rgba) return
|
|
555
|
+
$tempValue.value = rgba
|
|
556
|
+
})
|
|
446
557
|
|
|
447
558
|
const invertColors = computed(() => !!(localColor.percent.v < 50 || (localColor.val.a === undefined || localColor.val.a < 0.5)))
|
|
448
559
|
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { clampNumber } from "@alanscodelog/utils/clampNumber.js";
|
|
2
|
+
import Color from "colorjs.io";
|
|
3
|
+
export function safeConvertToHsva(val, allowAlpha) {
|
|
4
|
+
try {
|
|
5
|
+
const color = typeof val === "string" ? new Color(val) : new Color("srgb", [val.r / 255, val.g / 255, val.b / 255], allowAlpha ? val.a : 1);
|
|
6
|
+
const hsv = color.hsv;
|
|
7
|
+
if (!hsv || hsv[1] === void 0 || hsv[2] === void 0) return void 0;
|
|
8
|
+
const final = {
|
|
9
|
+
h: clampNumber(hsv[0] ?? 0, 0, Number.MAX_SAFE_INTEGER),
|
|
10
|
+
s: clampNumber(hsv[1], 0, 100),
|
|
11
|
+
v: clampNumber(hsv[2], 0, 100),
|
|
12
|
+
a: clampNumber(allowAlpha ? color.alpha : 1, 0, 1)
|
|
13
|
+
};
|
|
14
|
+
return final;
|
|
15
|
+
} catch {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { clampNumber } from "@alanscodelog/utils/clampNumber.js";
|
|
2
|
+
import Color from "colorjs.io";
|
|
3
|
+
export function safeConvertToRgba(val, allowAlpha) {
|
|
4
|
+
try {
|
|
5
|
+
const color = typeof val === "string" ? new Color(val) : new Color("hsv", [val.h, val.s, val.v], allowAlpha ? val.a : 1);
|
|
6
|
+
const rgb = color.srgb;
|
|
7
|
+
if (!rgb || rgb[0] === void 0 || rgb[1] === void 0 || rgb[2] === void 0) return void 0;
|
|
8
|
+
return {
|
|
9
|
+
r: clampNumber(rgb[0] / 1 * 255, 0, 255),
|
|
10
|
+
g: clampNumber(rgb[1] / 1 * 255, 0, 255),
|
|
11
|
+
b: clampNumber(rgb[2] / 1 * 255, 0, 255),
|
|
12
|
+
a: clampNumber(allowAlpha ? color.alpha : 1, 0, 1)
|
|
13
|
+
};
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { truncate } from "./truncate.js";
|
|
2
|
+
export function toLowPrecisionRgbaString(rgba, withAlpha, precision) {
|
|
3
|
+
const r = truncate(rgba.r, precision);
|
|
4
|
+
const g = truncate(rgba.g, precision);
|
|
5
|
+
const b = truncate(rgba.b, precision);
|
|
6
|
+
const a = rgba.a !== void 0 ? truncate(rgba.a, precision) : void 0;
|
|
7
|
+
return withAlpha ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function truncate(val: number, precision: number): string;
|