v-uixy 1.3.0 → 1.4.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/package.json +1 -1
- package/templates/assets/css/main.css +8 -0
- package/templates/components/Avatar/Avatar.component.vue +7 -3
- package/templates/components/Avatar/Avatar.styles.ts +3 -3
- package/templates/components/Badge/Badge.styles.ts +2 -2
- package/templates/components/Button/Button.styles.ts +3 -3
- package/templates/components/Calendar/Calendar.component.vue +26 -15
- package/templates/components/Calendar/Calendar.styles.ts +11 -3
- package/templates/components/Calendar/Calendar.types.ts +1 -0
- package/templates/components/ColorPicker/ColorPicker.component.vue +287 -0
- package/templates/components/ColorPicker/ColorPicker.types.ts +3 -0
- package/templates/components/ColorPicker/index.ts +4 -0
- package/templates/components/Command/Command.component.vue +6 -6
- package/templates/components/Command/Command.types.ts +1 -0
- package/templates/components/Input/Input.component.vue +14 -4
- package/templates/components/Input/Input.styles.ts +4 -4
- package/templates/components/Modal/Modal.component.vue +28 -4
- package/templates/components/Modal/Modal.types.ts +3 -1
- package/templates/components/Popover/Popover.component.vue +33 -2
- package/templates/components/Popover/Popover.types.ts +1 -0
- package/templates/components/Select/Select.component.vue +241 -87
- package/templates/components/Select/Select.styles.ts +5 -5
- package/templates/components/Select/Select.types.ts +12 -1
- package/templates/components/Select/SelectTreeItem.vue +154 -0
- package/templates/components/Select/composables/useSelect.composable.ts +260 -42
- package/templates/components/Sheet/Sheet.component.vue +6 -0
- package/templates/components/Tabs/Tabs.component.vue +11 -2
- package/templates/components/Tabs/Tabs.types.ts +1 -0
- package/templates/components/Textarea/Textarea.component.vue +10 -13
- package/templates/components/Textarea/Textarea.styles.ts +3 -3
- package/templates/components/TimePicker/TimePicker.component.vue +21 -18
- package/templates/components/TimePicker/TimePicker.styles.ts +1 -1
- package/templates/components/TimePicker/TimePicker.types.ts +1 -0
- package/templates/components/index.ts +35 -0
- package/templates/components.json +9 -3
- package/templates/composables/useInputValidation.ts +89 -0
- package/templates/composables/usePosition.ts +17 -2
package/package.json
CHANGED
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
@apply bg-transparent;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
160
|
+
display: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
159
163
|
.slider::-webkit-slider-thumb {
|
|
160
164
|
@apply relative z-10 h-5 w-5 cursor-pointer appearance-none rounded-full border-2 border-solid border-black bg-gray-100 dark:border-gray-100 dark:bg-black;
|
|
161
165
|
}
|
|
@@ -164,6 +168,10 @@
|
|
|
164
168
|
@apply cursor-default border-gray-500 dark:border-gray-500;
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
.slider-color-picker::-webkit-slider-thumb {
|
|
172
|
+
@apply relative z-10 size-4.5 cursor-pointer appearance-none rounded-full border-2 border-solid border-gray-900 bg-gray-100;
|
|
173
|
+
}
|
|
174
|
+
|
|
167
175
|
.disable-transitions * {
|
|
168
176
|
transition: none !important;
|
|
169
177
|
}
|
|
@@ -8,11 +8,11 @@ export const avatarWrapperStyles = styles(
|
|
|
8
8
|
squared: "rounded-2",
|
|
9
9
|
default: "",
|
|
10
10
|
},
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
export const avatarImageStyles = styles(
|
|
15
|
-
"flex aspect-square items-center overflow-hidden text-center text-[0]",
|
|
15
|
+
"flex aspect-square object-cover items-center overflow-hidden text-center text-[0]",
|
|
16
16
|
{
|
|
17
17
|
show: {
|
|
18
18
|
true: "",
|
|
@@ -23,5 +23,5 @@ export const avatarImageStyles = styles(
|
|
|
23
23
|
squared: "rounded-2",
|
|
24
24
|
default: "",
|
|
25
25
|
},
|
|
26
|
-
}
|
|
26
|
+
},
|
|
27
27
|
);
|
|
@@ -5,9 +5,9 @@ export const badgeStyles = styles(
|
|
|
5
5
|
{
|
|
6
6
|
variant: {
|
|
7
7
|
primary:
|
|
8
|
-
"bg-gray-800 hover:bg-gray-700 dark:hover:bg-gray-200 dark:bg-gray-300 text-gray-100 dark:text-black dark:disabled:bg-gray-400 dark:disabled:text-gray-500 disabled:text-gray-600",
|
|
8
|
+
"bg-gray-800 hover:bg-gray-700 border border-transparent dark:hover:bg-gray-200 dark:bg-gray-300 text-gray-100 dark:text-black dark:disabled:bg-gray-400 dark:disabled:text-gray-500 disabled:text-gray-600",
|
|
9
9
|
secondary:
|
|
10
|
-
"bg-gray-300 hover:bg-gray-200 dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-gray-100 text-black dark:disabled:bg-gray-600 dark:disabled:text-gray-700 disabled:text-gray-500",
|
|
10
|
+
"bg-gray-300 hover:bg-gray-200 border border-transparent dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-gray-100 text-black dark:disabled:bg-gray-600 dark:disabled:text-gray-700 disabled:text-gray-500",
|
|
11
11
|
tertiary:
|
|
12
12
|
"bg-transparent border border-solid border-gray-300 dark:border-gray-800 hover:bg-gray-200 dark:hover:bg-gray-800 text-black dark:text-gray-100 dark:disabled:border-gray-900 dark:disabled:text-gray-500 dark:disabled:bg-gray-800 disabled:bg-gray-300 disabled:text-gray-500 disabled:border-gray-400",
|
|
13
13
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styles from "~/utils/styles";
|
|
2
2
|
|
|
3
3
|
export const buttonStyles = styles(
|
|
4
|
-
"relative cursor-pointer h-8 font-500 transition-all duration-200 ease-in-out hover:shadow-xs flex items-center justify-center gap-2 disabled:pointer-events-none",
|
|
4
|
+
"relative cursor-pointer h-8 font-500 transition-all duration-200 ease-in-out hover:shadow-xs flex items-center justify-center gap-2 disabled:pointer-events-none border border-transparent",
|
|
5
5
|
{
|
|
6
6
|
variant: {
|
|
7
7
|
primary:
|
|
@@ -9,7 +9,7 @@ export const buttonStyles = styles(
|
|
|
9
9
|
secondary:
|
|
10
10
|
"bg-gray-300 hover:bg-gray-200 dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-gray-100 text-black dark:disabled:bg-gray-600 dark:disabled:text-gray-700 disabled:text-gray-500",
|
|
11
11
|
tertiary:
|
|
12
|
-
"bg-transparent border
|
|
12
|
+
"bg-transparent border-solid border-gray-300 dark:border-gray-800 hover:bg-gray-200 dark:hover:bg-gray-800 text-black dark:text-gray-100 dark:disabled:border-gray-900 dark:disabled:text-gray-500 dark:disabled:bg-gray-800 disabled:bg-gray-300 disabled:text-gray-500 disabled:border-gray-400",
|
|
13
13
|
quaternary:
|
|
14
14
|
"dark:hover:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-800 disabled:text-gray-700",
|
|
15
15
|
},
|
|
@@ -22,7 +22,7 @@ export const buttonStyles = styles(
|
|
|
22
22
|
true: "rounded-full",
|
|
23
23
|
false: "rounded-1",
|
|
24
24
|
},
|
|
25
|
-
}
|
|
25
|
+
},
|
|
26
26
|
);
|
|
27
27
|
|
|
28
28
|
export const iconStyles = styles("h-5 w-5 shrink-0", {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
size="sm"
|
|
8
8
|
variant="tertiary"
|
|
9
9
|
/>
|
|
10
|
-
<p class="w-
|
|
10
|
+
<p class="w-40 text-center text-sm font-500">
|
|
11
11
|
{{ MONTHS[selectedMonth] }} {{ selectedYear }}
|
|
12
12
|
</p>
|
|
13
13
|
<uixy-icon-button
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
class="grid w-full grid-cols-7 text-xs text-gray-400 dark:text-gray-700"
|
|
23
23
|
>
|
|
24
24
|
<div v-for="day in adjustedDays" :key="day" class="py-2 text-center">
|
|
25
|
-
{{ day
|
|
25
|
+
{{ day?.slice(0, 2) }}
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
@@ -36,9 +36,18 @@
|
|
|
36
36
|
selected: model?.toDateString?.() === day.date.toDateString(),
|
|
37
37
|
disabled:
|
|
38
38
|
!!props.disabled || day.fromPreviousMonth || day.fromNextMonth,
|
|
39
|
+
greyedOut:
|
|
40
|
+
!!day.fromCurrentMonth &&
|
|
41
|
+
!!props.isDateDisabled &&
|
|
42
|
+
props.isDateDisabled(day.date),
|
|
43
|
+
today: today.toDateString() === day.date.toDateString(),
|
|
39
44
|
})
|
|
40
45
|
"
|
|
41
|
-
:disabled="
|
|
46
|
+
:disabled="
|
|
47
|
+
day.fromPreviousMonth ||
|
|
48
|
+
day.fromNextMonth ||
|
|
49
|
+
(!!props.isDateDisabled && props.isDateDisabled(day.date))
|
|
50
|
+
"
|
|
42
51
|
@click="handleDayClick(day.date)"
|
|
43
52
|
>
|
|
44
53
|
{{ day.day }}
|
|
@@ -82,11 +91,13 @@
|
|
|
82
91
|
"Saturday",
|
|
83
92
|
];
|
|
84
93
|
|
|
94
|
+
const today = new Date();
|
|
95
|
+
|
|
85
96
|
const selectedYear = ref(props.year ?? new Date().getFullYear());
|
|
86
97
|
const selectedMonth = ref(props.month ?? new Date().getMonth());
|
|
87
98
|
|
|
88
99
|
const adjustedDays = computed(() =>
|
|
89
|
-
props.startOfWeek === "Monday" ? [...DAYS.slice(1), DAYS[0]] : DAYS
|
|
100
|
+
props.startOfWeek === "Monday" ? [...DAYS.slice(1), DAYS[0]] : DAYS,
|
|
90
101
|
);
|
|
91
102
|
|
|
92
103
|
const getDaysInMonth = (year: number, month: number) =>
|
|
@@ -101,11 +112,11 @@
|
|
|
101
112
|
});
|
|
102
113
|
|
|
103
114
|
const daysInCurrentMonth = computed(() =>
|
|
104
|
-
getDaysInMonth(selectedYear.value, selectedMonth.value)
|
|
115
|
+
getDaysInMonth(selectedYear.value, selectedMonth.value),
|
|
105
116
|
);
|
|
106
117
|
|
|
107
118
|
const daysInPreviousMonth = computed(() =>
|
|
108
|
-
getDaysInMonth(selectedYear.value, selectedMonth.value - 1)
|
|
119
|
+
getDaysInMonth(selectedYear.value, selectedMonth.value - 1),
|
|
109
120
|
);
|
|
110
121
|
|
|
111
122
|
const prevMonthDaysToShow = computed(() =>
|
|
@@ -114,11 +125,11 @@
|
|
|
114
125
|
date: new Date(
|
|
115
126
|
selectedYear.value,
|
|
116
127
|
selectedMonth.value - 1,
|
|
117
|
-
daysInPreviousMonth.value - i
|
|
128
|
+
daysInPreviousMonth.value - i,
|
|
118
129
|
),
|
|
119
130
|
dayOfWeek: (firstDayOfMonth.value - 1 - i + 7) % 7,
|
|
120
131
|
fromPreviousMonth: true,
|
|
121
|
-
})).reverse()
|
|
132
|
+
})).reverse(),
|
|
122
133
|
);
|
|
123
134
|
|
|
124
135
|
const currentMonthDaysToShow = computed(() =>
|
|
@@ -128,18 +139,18 @@
|
|
|
128
139
|
dayOfWeek: new Date(
|
|
129
140
|
selectedYear.value,
|
|
130
141
|
selectedMonth.value,
|
|
131
|
-
i + 1
|
|
142
|
+
i + 1,
|
|
132
143
|
).getDay(),
|
|
133
144
|
fromCurrentMonth: true,
|
|
134
|
-
}))
|
|
145
|
+
})),
|
|
135
146
|
);
|
|
136
147
|
|
|
137
148
|
const lastDayOfMonth = computed(() =>
|
|
138
149
|
new Date(
|
|
139
150
|
selectedYear.value,
|
|
140
151
|
selectedMonth.value,
|
|
141
|
-
daysInCurrentMonth.value
|
|
142
|
-
).getDay()
|
|
152
|
+
daysInCurrentMonth.value,
|
|
153
|
+
).getDay(),
|
|
143
154
|
);
|
|
144
155
|
|
|
145
156
|
const nextMonthDaysToShow = computed(() => {
|
|
@@ -190,8 +201,8 @@
|
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
function handleDayClick(date: Date) {
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
204
|
+
if (props.disabled) return;
|
|
205
|
+
if (props.isDateDisabled?.(date)) return;
|
|
206
|
+
model.value = date;
|
|
196
207
|
}
|
|
197
208
|
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styles from "~/utils/styles";
|
|
2
2
|
|
|
3
3
|
export const calendarStyles = styles(
|
|
4
|
-
"flex w-fit flex-col items-center rounded-2 border border-gray-300 bg-gray-100 p-3 shadow-xs dark:border-gray-900 dark:bg-black"
|
|
4
|
+
"flex w-fit flex-col items-center rounded-2 border border-gray-300 bg-gray-100 p-3 shadow-xs dark:border-gray-900 dark:bg-black",
|
|
5
5
|
);
|
|
6
6
|
|
|
7
7
|
export const calendarDayStyles = styles(
|
|
@@ -9,7 +9,7 @@ export const calendarDayStyles = styles(
|
|
|
9
9
|
{
|
|
10
10
|
currentMonth: {
|
|
11
11
|
true: "dark:text-gray-300",
|
|
12
|
-
false: "text-gray-
|
|
12
|
+
false: "text-gray-400 dark:text-gray-600",
|
|
13
13
|
},
|
|
14
14
|
selected: {
|
|
15
15
|
true: "bg-black text-white hover:bg-black dark:bg-white dark:!text-black dark:hover:bg-white",
|
|
@@ -19,5 +19,13 @@ export const calendarDayStyles = styles(
|
|
|
19
19
|
true: "cursor-default pointer-events-none",
|
|
20
20
|
false: "cursor-pointer",
|
|
21
21
|
},
|
|
22
|
-
|
|
22
|
+
greyedOut: {
|
|
23
|
+
true: "opacity-35 cursor-not-allowed pointer-events-none",
|
|
24
|
+
false: "",
|
|
25
|
+
},
|
|
26
|
+
today: {
|
|
27
|
+
true: "ring-1 ring-gray-400 dark:ring-gray-600",
|
|
28
|
+
false: "",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
23
31
|
);
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<uixy-card class="p-2 flex flex-col gap-4">
|
|
3
|
+
<div
|
|
4
|
+
ref="svRef"
|
|
5
|
+
class="relative cursor-crosshair overflow-hidden rounded-1 min-w-64 h-48"
|
|
6
|
+
:style="svBackgroundStyle"
|
|
7
|
+
@mousedown="onSvMouseDown"
|
|
8
|
+
@touchstart.prevent="onSvTouchStart"
|
|
9
|
+
>
|
|
10
|
+
<div
|
|
11
|
+
class="pointer-events-none absolute -mt-2 -ml-2 size-4 rounded-full border-2 shadow-sm transition-colors border-white"
|
|
12
|
+
:style="svHandleStyle"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex flex-col gap-4">
|
|
16
|
+
<input
|
|
17
|
+
type="range"
|
|
18
|
+
min="0"
|
|
19
|
+
max="360"
|
|
20
|
+
step="1"
|
|
21
|
+
v-model.number="hue"
|
|
22
|
+
:disabled="props.disabled"
|
|
23
|
+
class="h-2.5 w-full appearance-none rounded-full outline-none slider-color-picker border border-gray-300 dark:border-gray-900"
|
|
24
|
+
:style="hueSliderStyle"
|
|
25
|
+
/>
|
|
26
|
+
<input
|
|
27
|
+
type="range"
|
|
28
|
+
min="0"
|
|
29
|
+
max="100"
|
|
30
|
+
step="1"
|
|
31
|
+
v-model.number="alpha"
|
|
32
|
+
:disabled="props.disabled"
|
|
33
|
+
class="h-2.5 w-full appearance-none rounded-full outline-none slider-color-picker border border-gray-300 dark:border-gray-900"
|
|
34
|
+
:style="alphaSliderStyle"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="flex gap-2 items-center mt-2 justify-between">
|
|
38
|
+
<uixy-card class="p-1">
|
|
39
|
+
<div
|
|
40
|
+
class="w-12 h-4 rounded-1 shadow-sm"
|
|
41
|
+
:style="{ backgroundColor: model }"
|
|
42
|
+
></div>
|
|
43
|
+
</uixy-card>
|
|
44
|
+
<p class="text-xs font-500">{{ model }}</p>
|
|
45
|
+
</div>
|
|
46
|
+
</uixy-card>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import type { UixyColorPickerProps } from "./ColorPicker.types";
|
|
51
|
+
import { UixyCard } from "../Card";
|
|
52
|
+
|
|
53
|
+
const props = defineProps<UixyColorPickerProps>();
|
|
54
|
+
|
|
55
|
+
const model = defineModel<string>({ default: "#ff0000" });
|
|
56
|
+
|
|
57
|
+
const hue = ref(0);
|
|
58
|
+
const sat = ref(100);
|
|
59
|
+
const val = ref(100);
|
|
60
|
+
const alpha = ref(100);
|
|
61
|
+
|
|
62
|
+
const clamp = (n: number, min: number, max: number) =>
|
|
63
|
+
Math.min(max, Math.max(min, n));
|
|
64
|
+
|
|
65
|
+
const componentToHex = (c: number) => c.toString(16).padStart(2, "0");
|
|
66
|
+
|
|
67
|
+
const rgbToHex = (r: number, g: number, b: number) =>
|
|
68
|
+
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
|
|
69
|
+
|
|
70
|
+
const rgbToHex8 = (r: number, g: number, b: number, a: number) =>
|
|
71
|
+
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(
|
|
72
|
+
b,
|
|
73
|
+
)}${componentToHex(clamp(a, 0, 255))}`;
|
|
74
|
+
|
|
75
|
+
const hexToRgbA = (
|
|
76
|
+
hex?: string,
|
|
77
|
+
): {
|
|
78
|
+
r: number;
|
|
79
|
+
g: number;
|
|
80
|
+
b: number;
|
|
81
|
+
a: number;
|
|
82
|
+
hasAlpha: boolean;
|
|
83
|
+
} | null => {
|
|
84
|
+
if (!hex) return null;
|
|
85
|
+
const cleaned = hex.trim().replace(/^#/, "");
|
|
86
|
+
if (cleaned.length === 6) {
|
|
87
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
88
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
89
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
90
|
+
return { r, g, b, a: 255, hasAlpha: false } as const;
|
|
91
|
+
}
|
|
92
|
+
if (cleaned.length === 8) {
|
|
93
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
94
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
95
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
96
|
+
const a = parseInt(cleaned.slice(6, 8), 16);
|
|
97
|
+
return { r, g, b, a, hasAlpha: true } as const;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const hsvToRgb = (h: number, s: number, v: number) => {
|
|
103
|
+
const c = v * s;
|
|
104
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
105
|
+
const m = v - c;
|
|
106
|
+
|
|
107
|
+
let r1 = 0,
|
|
108
|
+
g1 = 0,
|
|
109
|
+
b1 = 0;
|
|
110
|
+
|
|
111
|
+
if (h >= 0 && h < 60) {
|
|
112
|
+
r1 = c;
|
|
113
|
+
g1 = x;
|
|
114
|
+
b1 = 0;
|
|
115
|
+
} else if (h < 120) {
|
|
116
|
+
r1 = x;
|
|
117
|
+
g1 = c;
|
|
118
|
+
b1 = 0;
|
|
119
|
+
} else if (h < 180) {
|
|
120
|
+
r1 = 0;
|
|
121
|
+
g1 = c;
|
|
122
|
+
b1 = x;
|
|
123
|
+
} else if (h < 240) {
|
|
124
|
+
r1 = 0;
|
|
125
|
+
g1 = x;
|
|
126
|
+
b1 = c;
|
|
127
|
+
} else if (h < 300) {
|
|
128
|
+
r1 = x;
|
|
129
|
+
g1 = 0;
|
|
130
|
+
b1 = c;
|
|
131
|
+
} else {
|
|
132
|
+
r1 = c;
|
|
133
|
+
g1 = 0;
|
|
134
|
+
b1 = x;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
r: Math.round((r1 + m) * 255),
|
|
138
|
+
g: Math.round((g1 + m) * 255),
|
|
139
|
+
b: Math.round((b1 + m) * 255),
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const rgbToHsv = (r: number, g: number, b: number) => {
|
|
144
|
+
r /= 255;
|
|
145
|
+
g /= 255;
|
|
146
|
+
b /= 255;
|
|
147
|
+
const max = Math.max(r, g, b),
|
|
148
|
+
min = Math.min(r, g, b);
|
|
149
|
+
const d = max - min;
|
|
150
|
+
let h = 0;
|
|
151
|
+
if (d === 0) h = 0;
|
|
152
|
+
else if (max === r) h = ((g - b) / d) % 6;
|
|
153
|
+
else if (max === g) h = (b - r) / d + 2;
|
|
154
|
+
else h = (r - g) / d + 4;
|
|
155
|
+
h = Math.round(h * 60);
|
|
156
|
+
if (h < 0) h += 360;
|
|
157
|
+
const s = max === 0 ? 0 : d / max;
|
|
158
|
+
const v = max;
|
|
159
|
+
return { h, s, v };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const svRef = ref<HTMLElement | null>(null);
|
|
163
|
+
|
|
164
|
+
const rgb = computed(() =>
|
|
165
|
+
hsvToRgb(hue.value, sat.value / 100, val.value / 100),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const updatingFromModel = ref(false);
|
|
169
|
+
|
|
170
|
+
watch([hue, sat, val, alpha], () => {
|
|
171
|
+
if (updatingFromModel.value) return;
|
|
172
|
+
const a = Math.round((alpha.value / 100) * 255);
|
|
173
|
+
if (a === 255) {
|
|
174
|
+
model.value = rgbToHex(rgb.value.r, rgb.value.g, rgb.value.b);
|
|
175
|
+
} else {
|
|
176
|
+
model.value = rgbToHex8(rgb.value.r, rgb.value.g, rgb.value.b, a);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const applyModel = (value?: string) => {
|
|
181
|
+
const parsed = hexToRgbA(value ?? model.value ?? "#ff0000");
|
|
182
|
+
if (!parsed) return;
|
|
183
|
+
const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b);
|
|
184
|
+
updatingFromModel.value = true;
|
|
185
|
+
hue.value = hsv.h;
|
|
186
|
+
sat.value = Math.round(hsv.s * 100);
|
|
187
|
+
val.value = Math.round(hsv.v * 100);
|
|
188
|
+
alpha.value = clamp(Math.round((parsed.a / 255) * 100), 0, 100);
|
|
189
|
+
|
|
190
|
+
let normalized: string;
|
|
191
|
+
if (!parsed.hasAlpha || parsed.a === 255) {
|
|
192
|
+
normalized = rgbToHex(parsed.r, parsed.g, parsed.b);
|
|
193
|
+
} else {
|
|
194
|
+
normalized = rgbToHex8(parsed.r, parsed.g, parsed.b, parsed.a);
|
|
195
|
+
}
|
|
196
|
+
if (model.value !== normalized) model.value = normalized;
|
|
197
|
+
updatingFromModel.value = false;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
onMounted(() => applyModel());
|
|
201
|
+
watch(model, (v) => applyModel(v));
|
|
202
|
+
|
|
203
|
+
const svBackgroundStyle = computed(() => ({
|
|
204
|
+
background: `linear-gradient(to top, black, transparent), linear-gradient(to right, white, hsl(${hue.value}, 100%, 50%))`,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
const svHandleStyle = computed(() => ({
|
|
208
|
+
left: `${sat.value}%`,
|
|
209
|
+
top: `${100 - val.value}%`,
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
const hueSliderStyle = computed(() => ({
|
|
213
|
+
background:
|
|
214
|
+
"linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)",
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
const alphaSliderStyle = computed(() => {
|
|
218
|
+
const { r, g, b } = rgb.value;
|
|
219
|
+
return {
|
|
220
|
+
background: `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0), rgba(${r}, ${g}, ${b}, 1))`,
|
|
221
|
+
} as Record<string, string>;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const updateSvFromEvent = (e: MouseEvent) => {
|
|
225
|
+
if (!svRef.value) return;
|
|
226
|
+
const rect = svRef.value.getBoundingClientRect();
|
|
227
|
+
const x = clamp(((e.clientX - rect.left) / rect.width) * 100, 0, 100);
|
|
228
|
+
const y = clamp(((e.clientY - rect.top) / rect.height) * 100, 0, 100);
|
|
229
|
+
sat.value = Math.round(x);
|
|
230
|
+
val.value = Math.round(100 - y);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const updateSvFromTouch = (e: TouchEvent) => {
|
|
234
|
+
if (!svRef.value) return;
|
|
235
|
+
const t = e.touches?.[0] ?? e.changedTouches?.[0];
|
|
236
|
+
if (!t) return;
|
|
237
|
+
const rect = svRef.value.getBoundingClientRect();
|
|
238
|
+
const x = clamp(((t.clientX - rect.left) / rect.width) * 100, 0, 100);
|
|
239
|
+
const y = clamp(((t.clientY - rect.top) / rect.height) * 100, 0, 100);
|
|
240
|
+
sat.value = Math.round(x);
|
|
241
|
+
val.value = Math.round(100 - y);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
let dragging = false;
|
|
245
|
+
|
|
246
|
+
const onSvMouseDown = (e: MouseEvent) => {
|
|
247
|
+
if (props.disabled) return;
|
|
248
|
+
|
|
249
|
+
dragging = true;
|
|
250
|
+
|
|
251
|
+
updateSvFromEvent(e);
|
|
252
|
+
|
|
253
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
254
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
258
|
+
if (!dragging) return;
|
|
259
|
+
|
|
260
|
+
updateSvFromEvent(e);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const onMouseUp = () => {
|
|
264
|
+
dragging = false;
|
|
265
|
+
|
|
266
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
267
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const onSvTouchStart = (e: TouchEvent) => {
|
|
271
|
+
if (props.disabled) return;
|
|
272
|
+
|
|
273
|
+
updateSvFromTouch(e);
|
|
274
|
+
|
|
275
|
+
window.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
276
|
+
window.addEventListener("touchend", onTouchEnd);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const onTouchMove = (e: TouchEvent) => {
|
|
280
|
+
updateSvFromTouch(e);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const onTouchEnd = () => {
|
|
284
|
+
window.removeEventListener("touchmove", onTouchMove);
|
|
285
|
+
window.removeEventListener("touchend", onTouchEnd);
|
|
286
|
+
};
|
|
287
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<uixy-modal v-model="open">
|
|
2
|
+
<uixy-modal v-model="open" :persistent="props.persistent">
|
|
3
3
|
<uixy-card :class="commandStyles($attrs.class as string)">
|
|
4
4
|
<div class="flex items-center gap-2 p-3">
|
|
5
5
|
<label :for="id">
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
/>
|
|
23
23
|
</div>
|
|
24
24
|
<uixy-separator variant="horizontal" />
|
|
25
|
-
<div class="scrollbar max-h-
|
|
25
|
+
<div class="scrollbar max-h-80 overflow-y-auto">
|
|
26
26
|
<template v-for="option in filteredOptions" :key="option.label">
|
|
27
27
|
<template v-if="option.items.length > 0">
|
|
28
28
|
<ul class="flex flex-col gap-2 p-2">
|
|
@@ -79,13 +79,13 @@
|
|
|
79
79
|
props.options.map((option) => ({
|
|
80
80
|
label: option.label,
|
|
81
81
|
items: option.items.filter((item) =>
|
|
82
|
-
item.label.toLowerCase().includes(input.value?.toLowerCase() ?? "")
|
|
82
|
+
item.label.toLowerCase().includes(input.value?.toLowerCase() ?? ""),
|
|
83
83
|
),
|
|
84
|
-
}))
|
|
84
|
+
})),
|
|
85
85
|
);
|
|
86
86
|
|
|
87
87
|
const isEmpty = computed(() =>
|
|
88
|
-
filteredOptions.value.every((option) => option.items.length === 0)
|
|
88
|
+
filteredOptions.value.every((option) => option.items.length === 0),
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
const handleClose = () => {
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
const placeholder = computed(
|
|
96
|
-
() => props.placeholder || "Search or type a command..."
|
|
96
|
+
() => props.placeholder || "Search or type a command...",
|
|
97
97
|
);
|
|
98
98
|
|
|
99
99
|
const emptyText = computed(() => props.emptyText || "No results found.");
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<div class="flex flex-col gap-1">
|
|
3
3
|
<div class="relative flex flex-col-reverse gap-1">
|
|
4
4
|
<input
|
|
5
|
+
ref="inputRef"
|
|
5
6
|
v-model="model"
|
|
6
7
|
:type="props.type ?? 'text'"
|
|
7
8
|
:id
|
|
8
9
|
:disabled="props.disabled"
|
|
9
10
|
:placeholder="props.placeholder"
|
|
10
|
-
:auto-focus="props.autoFocus"
|
|
11
11
|
:class="inputStyles({ status, icon }, $attrs.class as string)"
|
|
12
12
|
v-bind="filteredAttrs"
|
|
13
13
|
/>
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
return rest;
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
const inputRef = ref<HTMLInputElement>();
|
|
74
|
+
|
|
73
75
|
const model = defineModel<string>();
|
|
74
76
|
|
|
75
77
|
const instance = getCurrentInstance();
|
|
@@ -79,7 +81,7 @@
|
|
|
79
81
|
const hasIconClickEmit = computed(() => !!instance?.vnode.props?.onIconClick);
|
|
80
82
|
|
|
81
83
|
const status = computed(() =>
|
|
82
|
-
props.disabled ? "disabled" : props.status ?? "default"
|
|
84
|
+
props.disabled ? "disabled" : (props.status ?? "default"),
|
|
83
85
|
);
|
|
84
86
|
|
|
85
87
|
const text = computed(
|
|
@@ -89,10 +91,18 @@
|
|
|
89
91
|
default: props.helperText,
|
|
90
92
|
disabled: "",
|
|
91
93
|
valid: "",
|
|
92
|
-
}[props.status ?? "default"]
|
|
94
|
+
})[props.status ?? "default"],
|
|
93
95
|
);
|
|
94
96
|
|
|
95
97
|
const icon = computed(() =>
|
|
96
|
-
props.icon ? props.iconPositon ?? "right" : "none"
|
|
98
|
+
props.icon ? (props.iconPositon ?? "right") : "none",
|
|
97
99
|
);
|
|
100
|
+
|
|
101
|
+
onMounted(() => {
|
|
102
|
+
if (props.autoFocus) {
|
|
103
|
+
nextTick(() => inputRef.value?.focus({ preventScroll: true }));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
defineExpose({ focus: () => inputRef.value?.focus({ preventScroll: true }) });
|
|
98
108
|
</script>
|