From dd2d8eae51068a08dd1728f8be2d65e3dbc1bfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Mog=C3=A8re?= <heristop@yahoo.fr> Date: Sat, 28 Sep 2024 00:47:45 +0200 Subject: [PATCH] feat(config): added color picker --- app/components/AppFooter.vue | 8 +- app/components/EditableLabel.vue | 59 ++--- app/components/ProjectPanel.vue | 1 - app/components/TreeNode.vue | 48 ++-- app/components/config/StatusManager.vue | 318 ++++++++++++++++++------ app/composables/store.ts | 6 - app/pages/index.vue | 2 +- package.json | 3 +- pnpm-lock.yaml | 102 ++++++++ 9 files changed, 408 insertions(+), 139 deletions(-) diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 9e48391..33ef47d 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -2,12 +2,14 @@ <template> <footer class="footer w-full text-center py-4"> - <p class="flex items-center align-middle justify-center text-stone-800 dark:text-white text-sm justify-center gap-2"> - Made by <a + <p class="flex items-center justify-center text-stone-800 dark:text-white text-sm gap-2"> + <span>Made by <a href="https://heristop.github.io/about" target="_blank" class="text-stone-700 dark:text-stone-400 hover:text-stone-400 dark:hover:text-stone-500 transition-colors duration-300 font-bold" - >heristop</a> <span class="text-3xl front-bold">·</span> <span class="ml-1">Deployed on NuxtHub</span> + >heristop</a></span> + <span class="text-3xl font-bold">·</span> + <span>Deployed on NuxtHub</span> </p> </footer> </template> diff --git a/app/components/EditableLabel.vue b/app/components/EditableLabel.vue index fea7887..f0eb7a4 100644 --- a/app/components/EditableLabel.vue +++ b/app/components/EditableLabel.vue @@ -1,32 +1,50 @@ <script setup lang="ts"> -import { ref, onMounted, nextTick } from 'vue' +import { ref, watch } from 'vue' const props = defineProps<{ value: string isEditing: boolean + isEditMode: boolean }>() const emit = defineEmits<{ (e: 'update:value', value: string): void (e: 'update:isEditing', value: boolean): void + (e: 'double-click'): void }>() const inputRef = ref<HTMLInputElement | null>(null) const inputValue = ref(props.value) -onMounted(() => { - if (props.isEditing) { - nextTick(() => { +watch(() => props.isEditing, (newValue) => { + if (newValue) { + setTimeout(() => { inputRef.value?.focus() inputRef.value?.select() }) } }) +watch(() => props.value, (newValue) => { + inputValue.value = newValue +}) + const finishEditing = () => { emit('update:value', inputValue.value) emit('update:isEditing', false) } + +const cancelEditing = () => { + inputValue.value = props.value + emit('update:isEditing', false) +} + +const handleDoubleClick = (event: MouseEvent) => { + if (!props.isEditMode) { + event.stopPropagation() + emit('double-click') + } +} </script> <template> @@ -37,42 +55,13 @@ const finishEditing = () => { class="edit-input" @blur="finishEditing" @keyup.enter="finishEditing" - @keyup.esc="finishEditing" - @dbclick="finishEditing" + @keyup.esc="cancelEditing" > <span v-else class="node-text cursor-text" - @click="$emit('update:isEditing', true)" + @dblclick="handleDoubleClick" > {{ value }} </span> </template> - -<style scoped> -.edit-input { - text-align: center; - background: rgba(255, 255, 255, 0.1); - border: none; - border-bottom: 1px solid white; - color: white; - outline: none; - transition: all 0.3s; - padding: 2px 4px; - border-radius: 2px; -} - -.edit-input:focus { - background: rgba(255, 255, 255, 0.2); - border-bottom: 2px solid white; - outline: none; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.node-text { - text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; -} -</style> diff --git a/app/components/ProjectPanel.vue b/app/components/ProjectPanel.vue index 4294808..3baca01 100644 --- a/app/components/ProjectPanel.vue +++ b/app/components/ProjectPanel.vue @@ -506,7 +506,6 @@ onMounted(() => { class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" > <div class="bg-white dark:bg-stone-800 p-6 rounded-lg shadow-xl w-full max-w-lg relative"> - <!-- Bouton pour fermer la modale --> <button class="absolute top-4 right-4 text-stone-400 hover:text-stone-600 dark:text-stone-300 dark:hover:text-white transition-colors duration-300" aria-label="Close Modal" diff --git a/app/components/TreeNode.vue b/app/components/TreeNode.vue index 7c4941e..42a05e4 100644 --- a/app/components/TreeNode.vue +++ b/app/components/TreeNode.vue @@ -37,8 +37,11 @@ const displayContent = computed({ }, }) +const isDraggable = computed(() => !store.isEditingMode && !isEditing.value) + const handleLabelUpdate = (newValue: string) => { displayContent.value = newValue + isEditing.value = false } watch(() => store.displayLabel, () => { @@ -97,6 +100,12 @@ const getUniqueKeyName = (base: string, type: 'key' | 'name') => { return newName } +const handleLabelDoubleClick = () => { + if (!store.isEditingMode) { + isEditing.value = true + } +} + const addChildNode = (event: MouseEvent) => { event.stopPropagation() @@ -142,6 +151,11 @@ const deleteNode = (event: MouseEvent) => { } const handleDragStart = (event: DragEvent) => { + if (!isDraggable.value) { + event.preventDefault() + return + } + if (isDragging.value) { return } @@ -151,6 +165,11 @@ const handleDragStart = (event: DragEvent) => { } const handleDrop = (event: DragEvent) => { + if (store.isEditingMode) { + event.preventDefault() + return + } + event.preventDefault() event.stopPropagation() const draggedKey = event.dataTransfer?.getData('text/plain') @@ -161,22 +180,15 @@ const handleDrop = (event: DragEvent) => { } const handleDragOver = (event: DragEvent) => { - event.preventDefault() + if (!store.isEditingMode) { + event.preventDefault() + } } const handleTitleClick = (event: MouseEvent) => { event.stopPropagation() } -watch(() => [props.node.status, store.statuses], () => { - nodeStatus.value = props.node.status || store.statuses[0]?.name || '' - checkIfSuccessNode(props.node) -}, { immediate: true }) - -onMounted(() => { - updateParentStatus() -}) - const nodeStyle = computed(() => { const baseFlex = 1 const childCount = props.node.children ? props.node.children.length : 0 @@ -193,7 +205,6 @@ const handleClick = (event: MouseEvent) => { if (!props.node.children || !props.node.children.length) { updateStatus() - return } } @@ -232,6 +243,15 @@ const applySuccessAnimation = (node: Section) => { applyToParents(node) isSuccessNode.value = true } + +watch(() => [props.node.status, store.statuses], () => { + nodeStatus.value = props.node.status || store.statuses[0]?.name || '' + checkIfSuccessNode(props.node) +}, { immediate: true }) + +onMounted(() => { + updateParentStatus() +}) </script> <template> @@ -240,7 +260,7 @@ const applySuccessAnimation = (node: Section) => { :class="{ 'success-animation': isSuccessNode }" :style="nodeStyle" :data-node-key="node.key" - :draggable="!store.isEditingMode && !isEditing" + :draggable="isDraggable" @dragstart="handleDragStart" @drop="handleDrop" @dragover="handleDragOver" @@ -295,12 +315,12 @@ const applySuccessAnimation = (node: Section) => { </span> <EditableLabel - :key="store.displayLabel" :value="displayContent" :is-editing="isEditing" - class="truncate dark:text-stone-100" + :is-edit-mode="store.isEditingMode" @update:value="handleLabelUpdate" @update:is-editing="isEditing = $event" + @double-click="handleLabelDoubleClick" /> <div diff --git a/app/components/config/StatusManager.vue b/app/components/config/StatusManager.vue index 7f7765d..283cb4c 100644 --- a/app/components/config/StatusManager.vue +++ b/app/components/config/StatusManager.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { ref, computed, reactive } from 'vue' +import { ref, computed, reactive, nextTick } from 'vue' import { storeToRefs } from 'pinia' import { useStore, pastelColors } from '~/composables/store' import { useStatusManagement } from '~/composables/status' @@ -19,6 +19,9 @@ const store = useStore() const { statuses } = storeToRefs(store) const isDragging = ref(false) const editingStatus = ref<EditingStatus | null>(null) +const showColorPicker = ref(false) +const editingColorIndex = ref<number | null>(null) +const newStatusInput = ref<HTMLInputElement | null>(null) withDefaults(defineProps<{ draggable?: boolean @@ -30,9 +33,37 @@ const safeStatuses = computed<Status[]>(() => { return Array.isArray(statuses.value) ? statuses.value : [] }) +function getNextColor(): string { + const currentColors = safeStatuses.value.map((status: Status) => status.color) + const availableColors = pastelColors.filter(color => !currentColors.includes(color)) + + return availableColors.length > 0 ? availableColors[0] : pastelColors[0] +} + const newStatus = reactive<Status>({ name: '', - color: getNextColor() || '#FFFFFF', + color: getNextColor(), +}) + +const currentEditingColor = computed({ + get: () => { + if (editingColorIndex.value !== null) { + return safeStatuses.value[editingColorIndex.value].color + } + return newStatus.color + }, + set: (newColor: string) => { + if (editingColorIndex.value !== null) { + store.setStatuses( + safeStatuses.value.map((status, i) => + i === editingColorIndex.value ? { ...status, color: newColor } : status, + ), + ) + } + else { + newStatus.color = newColor + } + }, }) const addNewStatus = () => { @@ -40,7 +71,10 @@ const addNewStatus = () => { const updatedStatuses = [...safeStatuses.value, { name: newStatus.name, color: newStatus.color }] store.setStatuses(updatedStatuses) newStatus.name = '' - newStatus.color = getNextColor() || '#FFFFFF' + newStatus.color = getNextColor() + nextTick(() => { + newStatusInput.value?.focus() + }) } } @@ -99,103 +133,123 @@ const getTextColor = (backgroundColor: string): string => { const isValidColor = (color: string): boolean => /^#[0-9A-F]{6}$/i.test(color) -function getNextColor(): string { - const currentColors = safeStatuses.value.map((status: Status) => status.color) - const availableColors = pastelColors.filter(color => !currentColors.includes(color)) +const startColorEditing = (index: number | null) => { + editingColorIndex.value = index + showColorPicker.value = true +} - return availableColors.length > 0 ? availableColors[0] : pastelColors[0] +const updateStatusColor = (color: string) => { + currentEditingColor.value = color +} + +const closeColorPicker = () => { + showColorPicker.value = false + editingColorIndex.value = null } </script> <template> <div class="space-y-2"> - <div - v-for="(status, index) in safeStatuses" - :key="status.name" - class="flex items-center space-x-2 p-2 bg-stone-200/50 dark:bg-stone-800 rounded-lg shadow-sm transition-all duration-300 ease-in-out" - :class="{ 'cursor-grab active:cursor-grabbing hover:shadow-sm': draggable && !editingStatus }" - :draggable="draggable && !editingStatus" - @dragstart="startStatusDrag($event, index)" - @dragend="isDragging = false" - @dragover.prevent - @drop="dropStatus($event, index)" + <TransitionGroup + name="list" + tag="div" > - <div class="text-stone-600 dark:text-stone-300 w-20 text-xs flex items-center"> - <svg - v-if="draggable && !editingStatus" - xmlns="http://www.w3.org/2000/svg" - class="h-4 w-4 mr-1" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M4 6h16M4 12h16M4 18h16" - /> - </svg> - {{ index + 1 }}. - </div> - - <input - :value="editingStatus?.index === index ? editingStatus.name : status.name" - class="p-1 font-semibold rounded w-full placeholder:text-stone-300 text-xs transition-all duration-300 focus:ring-2 focus:ring-stone-500 bg-white dark:bg-stone-700 text-stone-800 dark:text-white" - placeholder="status" - @input="updateStatusName(index, ($event.target as HTMLInputElement).value)" - @focus="startEditing(index, status.name)" - @blur="stopEditing" - > - <input - v-model="status.color" - :style="{ backgroundColor: isValidColor(status.color) ? status.color : '#FFFFFF', color: isValidColor(status.color) ? getTextColor(status.color) : '#000000' }" - class="p-1 font-semibold rounded w-full placeholder:text-stone-300 text-xs transition-all duration-300 focus:ring-2 focus:ring-stone-500" - placeholder="color" - @focus="startEditing(index, status.name)" - @blur="stopEditing" - > - <button - :disabled="safeStatuses.length <= 1" - class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-110" - @click="removeStatus(index)" + <div + v-for="(status, index) in safeStatuses" + :key="status.name" + class="flex items-center space-x-2 p-2 bg-stone-200/50 dark:bg-stone-800 rounded-lg shadow-sm transition-all duration-300 ease-in-out hover:shadow-md" + :class="{ 'cursor-grab active:cursor-grabbing': draggable && !editingStatus }" + :draggable="draggable && !editingStatus" + @dragstart="startStatusDrag($event, index)" + @dragend="isDragging = false" + @dragover.prevent + @drop="dropStatus($event, index)" > - <svg - xmlns="http://www.w3.org/2000/svg" - class="h-5 w-5" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" + <div class="text-stone-600 dark:text-stone-300 w-20 text-xs flex items-center"> + <svg + v-if="draggable && !editingStatus" + xmlns="http://www.w3.org/2000/svg" + class="h-4 w-4 mr-1 cursor-grab" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M4 6h16M4 12h16M4 18h16" + /> + </svg> + {{ index + 1 }}. + </div> + + <input + :value="editingStatus?.index === index ? editingStatus.name : status.name" + class="p-1 font-semibold rounded w-full placeholder:text-stone-300 text-xs transition-all duration-300 focus:ring-2 focus:ring-stone-300 bg-white dark:bg-stone-700 text-stone-800 dark:text-white" + placeholder="status" + @input="updateStatusName(index, ($event.target as HTMLInputElement).value)" + @focus="startEditing(index, status.name)" + @blur="stopEditing" > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" - /> - </svg> - </button> - </div> + <div + :style="{ backgroundColor: isValidColor(status.color) ? status.color : '#FFFFFF', color: isValidColor(status.color) ? getTextColor(status.color) : '#000000' }" + class="p-1 font-semibold rounded w-12 h-8 cursor-pointer transition-all duration-300 hover:opacity-80 focus:ring-2 focus:ring-stone-300" + tabindex="0" + role="button" + aria-label="Change color" + @click="startColorEditing(index)" + @keydown.enter="startColorEditing(index)" + /> + <button + :disabled="safeStatuses.length <= 1" + class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-stone-300" + aria-label="Remove status" + @click="removeStatus(index)" + > + <svg + xmlns="http://www.w3.org/2000/svg" + class="h-5 w-5" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" + /> + </svg> + </button> + </div> + </TransitionGroup> </div> - <div class="flex items-center space-x-2 p-2 bg-stone-200/50 dark:bg-stone-800 rounded-lg shadow-sm mt-2 hover:shadow-sm"> + <div class="flex items-center space-x-2 p-2 bg-stone-200/50 dark:bg-stone-800 rounded-lg shadow-sm mt-2 hover:shadow-md transition-all duration-300"> <div class="text-stone-600 dark:text-stone-300 w-20 text-xs"> {{ safeStatuses.length + 1 }}. </div> <input + ref="newStatusInput" v-model="newStatus.name" class="p-1 font-semibold rounded w-full placeholder:text-stone-400 text-xs transition-all duration-300 focus:ring-2 focus:ring-stone-500 bg-white dark:bg-stone-700 text-stone-800 dark:text-white" placeholder="New Status" + @keyup.enter="addNewStatus" > - <input - v-model="newStatus.color" + <div :style="{ backgroundColor: isValidColor(newStatus.color) ? newStatus.color : '#FFFFFF', color: isValidColor(newStatus.color) ? getTextColor(newStatus.color) : '#000000' }" - class="p-1 font-semibold rounded w-full placeholder:text-stone-400 text-xs transition-all duration-300 focus:ring-2 focus:ring-stone-500" - :placeholder="getNextColor()" - > + class="p-1 font-semibold rounded w-12 h-8 cursor-pointer transition-all duration-300 hover:opacity-80 focus:ring-2 focus:ring-stone-300" + tabindex="0" + role="button" + aria-label="Choose color for new status" + @click="startColorEditing(null)" + @keydown.enter="startColorEditing(null)" + /> <button :disabled="!newStatus.name || !newStatus.color" - class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-110" + class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-stone-300" + aria-label="Add new status" @click="addNewStatus" > <svg @@ -214,4 +268,112 @@ function getNextColor(): string { </svg> </button> </div> + + <Teleport to="body"> + <Transition name="fade"> + <div + v-if="showColorPicker" + class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" + > + <div class="bg-stone-50 dark:bg-stone-800 p-4 rounded-lg shadow-lg m-4 max-w-sm w-full"> + <div class="flex justify-between items-center mb-4"> + <h3 class="text-lg font-semibold text-stone-800 dark:text-stone-200"> + Choose a Color + </h3> + <button + class="text-stone-400 hover:text-stone-600 dark:text-stone-300 dark:hover:text-white transition-colors duration-300" + aria-label="Close Color Picker" + @click="closeColorPicker" + > + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" + class="w-6 h-6" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M6 18L18 6M6 6l12 12" + /> + </svg> + </button> + </div> + + <div class="grid grid-cols-5 gap-2 mb-4"> + <button + v-for="color in pastelColors" + :key="color" + class="w-full pt-full rounded-full border border-stone-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-stone-400 transition-transform duration-200 hover:scale-110" + :style="{ backgroundColor: color }" + :aria-label="`Select color ${color}`" + @click="updateStatusColor(color)" + /> + </div> + + <div class="flex items-center space-x-2"> + <input + type="color" + :value="currentEditingColor" + class="w-10 h-10 rounded cursor-pointer" + @input="updateStatusColor(($event.target as HTMLInputElement).value)" + > + <input + v-model="currentEditingColor" + type="text" + class="flex-grow p-2 text-sm border rounded focus:outline-none focus:ring-2 focus:ring-stone-300 bg-white dark:bg-stone-700 text-stone-800 dark:text-white" + placeholder="#FFFFFF" + > + </div> + </div> + </div> + </Transition> + </Teleport> </template> + +<style scoped> +.list-move, +.list-enter-active, +.list-leave-active { + transition: all 0.5s ease; +} + +.list-enter-from, +.list-leave-to { + opacity: 0; + transform: translateX(30px); +} + +.list-leave-active { + position: absolute; +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.3s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.pt-full { + padding-top: 100%; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} +</style> diff --git a/app/composables/store.ts b/app/composables/store.ts index bc60ad9..ddd7d2a 100644 --- a/app/composables/store.ts +++ b/app/composables/store.ts @@ -19,12 +19,6 @@ export const pastelColors = [ '#D4A5A5', // Light Rose '#FFD1DC', // Light Pinkish '#B2B2B2', // Light Gray - '#FF6961', // Pastel Red - '#F49AC2', // Pastel Pink - '#77DD77', // Pastel Green - '#AEC6CF', // Pastel Blue - '#CFCFC4', // Pastel Gray - '#B19CD9', // Pastel Lilac ] type State = { diff --git a/app/pages/index.vue b/app/pages/index.vue index e5982c2..a3f71df 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -139,7 +139,7 @@ onMounted(() => { appear > <div class="text-center space-y-8 py-16"> - <h1 class="flex flex-col items-center justify-center text-center text-4xl font-bold mb-10 drop-shadow-lg bg-clip-text text-transparent bg-gradient-to-r from-[#DD5E89] to-[#F7BB97]"> + <h1 class="flex flex-row items-center justify-center text-center text-4xl font-bold mb-10 drop-shadow-lg bg-clip-text text-transparent bg-gradient-to-r from-[#DD5E89] to-[#F7BB97]"> <img alt="Clover Map Logo" src="@/assets/logo.svg" diff --git a/package.json b/package.json index 45ca30f..26676e6 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "nuxt": "^3.13.2", "nuxt-snackbar": "^1.0.4", "vue": "^3.5.7", - "vue-router": "^4.4.5" + "vue-router": "^4.4.5", + "vue3-colorpicker": "^2.3.0" }, "devDependencies": { "@commitlint/cli": "^19.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fbb4d1..1e655c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: vue-router: specifier: ^4.4.5 version: 4.4.5(vue@3.5.7(typescript@5.6.2)) + vue3-colorpicker: + specifier: ^2.3.0 + version: 2.3.0(@aesoper/normal-utils@0.1.5)(@popperjs/core@2.11.8)(@vueuse/core@10.11.1(vue@3.5.7(typescript@5.6.2)))(gradient-parser@1.0.2)(lodash-es@4.17.21)(tinycolor2@1.6.0)(vue-types@4.2.1(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2)) devDependencies: '@commitlint/cli': specifier: ^19.3.0 @@ -102,6 +105,9 @@ importers: packages: + '@aesoper/normal-utils@0.1.5': + resolution: {integrity: sha512-LFF/6y6h5mfwhnJaWqqxuC8zzDaHCG62kMRkd8xhDtq62TQj9dM17A9DhE87W7DhiARJsHLgcina/9P4eNCN1w==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -1109,6 +1115,9 @@ packages: '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@rollup/plugin-alias@5.1.0': resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} engines: {node: '>=14.0.0'} @@ -1312,6 +1321,9 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@typescript-eslint/eslint-plugin@7.18.0': resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1536,6 +1548,15 @@ packages: '@vue/shared@3.5.7': resolution: {integrity: sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g==} + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2678,6 +2699,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gradient-parser@1.0.2: + resolution: {integrity: sha512-gR6nY33xC9yJoH4wGLQtZQMXDi6RI3H37ERu7kQCVUzlXjNedpZM7xcA489Opwbq0BSGohtWGsWsntupmxelMg==} + engines: {node: '>=0.10.0'} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -2900,6 +2925,10 @@ packages: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -3127,6 +3156,9 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -4368,6 +4400,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.0: resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} @@ -4758,6 +4793,24 @@ packages: peerDependencies: vue: ^3.2.0 + vue-types@4.2.1: + resolution: {integrity: sha512-DNQZmJuOvovLUIp0BENRkdnZHbI0V4e2mNvjAZOAXKD56YGvRchtUYOXA/XqTxdv7Ng5SJLZqRKRpAhm5NLaPQ==} + engines: {node: '>=12.16.0'} + peerDependencies: + vue: ^2.0.0 || ^3.0.0 + + vue3-colorpicker@2.3.0: + resolution: {integrity: sha512-e3lLmBcy7mkRrNQVeUny1DjOd6E11L8H5ok5Bx4MdXmrG+RzyacRF7KkhrEWmRYPhKAsaoUrWsFkmpPAaYnE5A==} + peerDependencies: + '@aesoper/normal-utils': ^0.1.5 + '@popperjs/core': ^2.11.8 + '@vueuse/core': ^10.1.2 + gradient-parser: ^1.0.2 + lodash-es: ^4.17.21 + tinycolor2: ^1.4.2 + vue: ^3.2.6 + vue-types: ^4.1.0 + vue3-icon@2.1.0: resolution: {integrity: sha512-cnFiGAEwzp/KQKody2Yj8cBDP4Kez0AUp5mDnp052FA1fECl8a9uYUKLaeRdH0JakmZ7Jfp3tdHbpBEWF9sgBA==} @@ -4888,6 +4941,8 @@ packages: snapshots: + '@aesoper/normal-utils@0.1.5': {} + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -6046,6 +6101,8 @@ snapshots: '@polka/url@1.0.0-next.28': {} + '@popperjs/core@2.11.8': {} + '@rollup/plugin-alias@5.1.0(rollup@4.22.2)': dependencies: slash: 4.0.0 @@ -6211,6 +6268,8 @@ snapshots: '@types/resolve@1.20.2': {} + '@types/web-bluetooth@0.0.20': {} + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.1 @@ -6575,6 +6634,25 @@ snapshots: '@vue/shared@3.5.7': {} + '@vueuse/core@10.11.1(vue@3.5.7(typescript@5.6.2))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.7(typescript@5.6.2)) + vue-demi: 0.14.10(vue@3.5.7(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/shared@10.11.1(vue@3.5.7(typescript@5.6.2))': + dependencies: + vue-demi: 0.14.10(vue@3.5.7(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -7818,6 +7896,8 @@ snapshots: graceful-fs@4.2.11: {} + gradient-parser@1.0.2: {} + graphemer@1.4.0: {} gzip-size@7.0.0: @@ -8028,6 +8108,8 @@ snapshots: is-path-inside@4.0.0: {} + is-plain-object@5.0.0: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.6 @@ -8295,6 +8377,8 @@ snapshots: dependencies: p-locate: 6.0.0 + lodash-es@4.17.21: {} + lodash.camelcase@4.3.0: {} lodash.defaults@4.2.0: {} @@ -9690,6 +9774,8 @@ snapshots: tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.0: {} tinyglobby@0.2.6: @@ -10098,6 +10184,22 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.7(typescript@5.6.2) + vue-types@4.2.1(vue@3.5.7(typescript@5.6.2)): + dependencies: + is-plain-object: 5.0.0 + vue: 3.5.7(typescript@5.6.2) + + vue3-colorpicker@2.3.0(@aesoper/normal-utils@0.1.5)(@popperjs/core@2.11.8)(@vueuse/core@10.11.1(vue@3.5.7(typescript@5.6.2)))(gradient-parser@1.0.2)(lodash-es@4.17.21)(tinycolor2@1.6.0)(vue-types@4.2.1(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2)): + dependencies: + '@aesoper/normal-utils': 0.1.5 + '@popperjs/core': 2.11.8 + '@vueuse/core': 10.11.1(vue@3.5.7(typescript@5.6.2)) + gradient-parser: 1.0.2 + lodash-es: 4.17.21 + tinycolor2: 1.6.0 + vue: 3.5.7(typescript@5.6.2) + vue-types: 4.2.1(vue@3.5.7(typescript@5.6.2)) + vue3-icon@2.1.0: {} vue3-snackbar@2.3.4(vue@3.5.7(typescript@5.6.2)):